1 /**
2 * Copyright (c) 2020, Timothy Stack
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * * Redistributions of source code must retain the above copyright notice, this
10 * list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 * * Neither the name of Timothy Stack nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 *
29 * @file spectro_source.cc
30 */
31
32 #include "config.h"
33
34 #include "base/math_util.hh"
35 #include "spectro_source.hh"
36
list_input_handle_key(listview_curses & lv,int ch)37 bool spectrogram_source::list_input_handle_key(listview_curses &lv, int ch)
38 {
39 switch (ch) {
40 case 'm': {
41 if (this->ss_cursor_top < 0 ||
42 (size_t) this->ss_cursor_top >= this->text_line_count() ||
43 this->ss_cursor_column == -1 ||
44 this->ss_value_source == nullptr) {
45 alerter::singleton().chime();
46 return true;
47 }
48
49 unsigned long width;
50 vis_line_t height;
51
52 lv.get_dimensions(height, width);
53
54 spectrogram_bounds &sb = this->ss_cached_bounds;
55 auto begin_time_opt = this->time_for_row(this->ss_cursor_top);
56 if (!begin_time_opt) {
57 return true;
58 }
59 auto begin_time = begin_time_opt.value();
60 struct timeval end_time = begin_time;
61
62 end_time.tv_sec += this->ss_granularity;
63 double range_min, range_max, column_size;
64
65 column_size = (sb.sb_max_value_out - sb.sb_min_value_out) /
66 (double) (width - 1);
67 range_min = sb.sb_min_value_out + this->ss_cursor_column * column_size;
68 range_max = range_min + column_size + column_size * 0.01;
69 this->ss_value_source->spectro_mark((textview_curses &) lv,
70 begin_time.tv_sec, end_time.tv_sec,
71 range_min, range_max);
72 this->invalidate();
73 lv.reload_data();
74 return true;
75 }
76 case KEY_LEFT:
77 case KEY_RIGHT: {
78 unsigned long width;
79 vis_line_t height;
80 string_attrs_t sa;
81
82 this->ss_cursor_top = lv.get_top();
83 lv.get_dimensions(height, width);
84
85 this->text_attrs_for_line((textview_curses &) lv, this->ss_cursor_top, sa);
86
87 if (sa.empty()) {
88 this->ss_cursor_column = -1;
89 return true;
90 }
91
92 string_attrs_t::iterator current;
93
94 struct line_range lr(this->ss_cursor_column, this->ss_cursor_column + 1);
95
96 current = find_string_attr(sa, lr);
97
98 if (current != sa.end()) {
99 if (ch == KEY_LEFT) {
100 if (current == sa.begin()) {
101 current = sa.end();
102 }
103 else {
104 --current;
105 }
106 }
107 else {
108 ++current;
109 }
110 }
111
112 if (current == sa.end()) {
113 if (ch == KEY_LEFT) {
114 current = sa.end();
115 --current;
116 }
117 else {
118 current = sa.begin();
119 }
120 }
121 this->ss_cursor_column = current->sa_range.lr_start;
122
123 lv.reload_data();
124
125 return true;
126 }
127 default:
128 return false;
129 }
130 }
131
132 bool
list_value_for_overlay(const listview_curses & lv,int y,int bottom,vis_line_t row,attr_line_t & value_out)133 spectrogram_source::list_value_for_overlay(const listview_curses &lv, int y,
134 int bottom, vis_line_t row,
135 attr_line_t &value_out)
136 {
137 if (y != 0) {
138 return false;
139 }
140
141 std::string &line = value_out.get_string();
142 char buf[128];
143 vis_line_t height;
144 unsigned long width;
145
146 lv.get_dimensions(height, width);
147
148 this->cache_bounds();
149
150 if (this->ss_cached_line_count == 0) {
151 value_out.with_ansi_string(
152 ANSI_ROLE("error: no log data"),
153 view_colors::VCR_ERROR);
154 return true;
155 }
156
157 spectrogram_bounds &sb = this->ss_cached_bounds;
158 spectrogram_thresholds &st = this->ss_cached_thresholds;
159
160 snprintf(buf, sizeof(buf), "Min: %'.10lg", sb.sb_min_value_out);
161 line = buf;
162
163 snprintf(buf, sizeof(buf),
164 ANSI_ROLE(" ") " 1-%'d "
165 ANSI_ROLE(" ") " %'d-%'d "
166 ANSI_ROLE(" ") " %'d+",
167 view_colors::VCR_LOW_THRESHOLD,
168 st.st_green_threshold - 1,
169 view_colors::VCR_MED_THRESHOLD,
170 st.st_green_threshold,
171 st.st_yellow_threshold - 1,
172 view_colors::VCR_HIGH_THRESHOLD,
173 st.st_yellow_threshold);
174 line.append(width / 2 - strlen(buf) / 3 - line.length(), ' ');
175 line.append(buf);
176 scrub_ansi_string(line, value_out.get_attrs());
177
178 snprintf(buf, sizeof(buf), "Max: %'.10lg", sb.sb_max_value_out);
179 line.append(width - strlen(buf) - line.length() - 2, ' ');
180 line.append(buf);
181
182 value_out.with_attr(string_attr(
183 line_range(0, -1),
184 &view_curses::VC_STYLE,
185 A_UNDERLINE));
186
187 return true;
188 }
189
text_line_count()190 size_t spectrogram_source::text_line_count()
191 {
192 if (this->ss_value_source == nullptr) {
193 return 0;
194 }
195
196 this->cache_bounds();
197
198 return this->ss_cached_line_count;
199 }
200
text_line_width(textview_curses & tc)201 size_t spectrogram_source::text_line_width(textview_curses &tc)
202 {
203 if (tc.get_window() == nullptr) {
204 return 80;
205 }
206
207 unsigned long width;
208 vis_line_t height;
209
210 tc.get_dimensions(height, width);
211 return width;
212 }
213
time_for_row(vis_line_t row)214 nonstd::optional<struct timeval> spectrogram_source::time_for_row(vis_line_t row)
215 {
216 struct timeval retval { 0, 0 };
217
218 this->cache_bounds();
219 retval.tv_sec =
220 rounddown(this->ss_cached_bounds.sb_begin_time, this->ss_granularity) +
221 row * this->ss_granularity;
222
223 return retval;
224 }
225
row_for_time(struct timeval time_bucket)226 nonstd::optional<vis_line_t> spectrogram_source::row_for_time(struct timeval time_bucket)
227 {
228 if (this->ss_value_source == nullptr) {
229 return nonstd::nullopt;
230 }
231
232 time_t diff;
233 int retval;
234
235 this->cache_bounds();
236 if (time_bucket.tv_sec < this->ss_cached_bounds.sb_begin_time) {
237 return 0_vl;
238 }
239
240 diff = time_bucket.tv_sec - this->ss_cached_bounds.sb_begin_time;
241 retval = diff / this->ss_granularity;
242
243 return vis_line_t(retval);
244 }
245
text_value_for_line(textview_curses & tc,int row,std::string & value_out,text_sub_source::line_flags_t flags)246 void spectrogram_source::text_value_for_line(textview_curses &tc, int row,
247 std::string &value_out,
248 text_sub_source::line_flags_t flags)
249 {
250 spectrogram_row &s_row = this->load_row(tc, row);
251
252 char tm_buffer[128];
253 struct tm tm;
254
255 auto row_time_opt = this->time_for_row(vis_line_t(row));
256 if (!row_time_opt) {
257 value_out.clear();
258 return;
259 }
260 auto row_time = row_time_opt.value();
261
262 gmtime_r(&row_time.tv_sec, &tm);
263 strftime(tm_buffer, sizeof(tm_buffer), " %a %b %d %H:%M:%S", &tm);
264
265 value_out = tm_buffer;
266 value_out.resize(s_row.sr_width, ' ');
267
268 for (size_t lpc = 0; lpc <= s_row.sr_width; lpc++) {
269 if (s_row.sr_values[lpc].rb_marks) {
270 value_out[lpc] = 'x';
271 }
272 }
273
274 if (this->ss_cursor_top == row && this->ss_cursor_column != -1) {
275 if (value_out[this->ss_cursor_column] == 'x') {
276 value_out[this->ss_cursor_column] = '*';
277 }
278 else {
279 value_out[this->ss_cursor_column] = '+';
280 }
281 }
282 }
283
text_attrs_for_line(textview_curses & tc,int row,string_attrs_t & value_out)284 void spectrogram_source::text_attrs_for_line(textview_curses &tc, int row,
285 string_attrs_t &value_out)
286 {
287 if (this->ss_value_source == nullptr) {
288 return;
289 }
290
291 view_colors &vc = view_colors::singleton();
292 spectrogram_thresholds &st = this->ss_cached_thresholds;
293 spectrogram_row &s_row = this->load_row(tc, row);
294
295 for (int lpc = 0; lpc <= (int) s_row.sr_width; lpc++) {
296 int col_value = s_row.sr_values[lpc].rb_counter;
297
298 if (col_value == 0) {
299 continue;
300 }
301
302 int color;
303
304 if (col_value < st.st_green_threshold) {
305 color = COLOR_GREEN;
306 }
307 else if (col_value < st.st_yellow_threshold) {
308 color = COLOR_YELLOW;
309 }
310 else {
311 color = COLOR_RED;
312 }
313 value_out.emplace_back(
314 line_range(lpc, lpc + 1),
315 &view_curses::VC_STYLE,
316 vc.ansi_color_pair(COLOR_BLACK, color)
317 );
318 }
319 }
320
cache_bounds()321 void spectrogram_source::cache_bounds()
322 {
323 if (this->ss_value_source == nullptr) {
324 this->ss_cached_bounds.sb_count = 0;
325 this->ss_cached_bounds.sb_begin_time = 0;
326 return;
327 }
328
329 spectrogram_bounds sb;
330
331 this->ss_value_source->spectro_bounds(sb);
332
333 if (sb.sb_count == this->ss_cached_bounds.sb_count) {
334 return;
335 }
336
337 this->ss_cached_bounds = sb;
338
339 if (sb.sb_count == 0) {
340 this->ss_cached_line_count = 0;
341 return;
342 }
343
344 time_t grain_begin_time = rounddown(sb.sb_begin_time, this->ss_granularity);
345 time_t grain_end_time = roundup_size(sb.sb_end_time, this->ss_granularity);
346
347 time_t diff = std::max((time_t) 1, grain_end_time - grain_begin_time);
348 this->ss_cached_line_count =
349 (diff + this->ss_granularity - 1) / this->ss_granularity;
350
351 int64_t samples_per_row = sb.sb_count / this->ss_cached_line_count;
352 spectrogram_thresholds &st = this->ss_cached_thresholds;
353
354 st.st_yellow_threshold = samples_per_row / 2;
355 st.st_green_threshold = st.st_yellow_threshold / 2;
356
357 if (st.st_green_threshold <= 1) {
358 st.st_green_threshold = 2;
359 }
360 if (st.st_yellow_threshold <= st.st_green_threshold) {
361 st.st_yellow_threshold = st.st_green_threshold + 1;
362 }
363 }
364
load_row(textview_curses & tc,int row)365 spectrogram_row &spectrogram_source::load_row(textview_curses &tc, int row)
366 {
367 this->cache_bounds();
368
369 unsigned long width;
370 vis_line_t height;
371
372 tc.get_dimensions(height, width);
373 width -= 2;
374
375 spectrogram_bounds &sb = this->ss_cached_bounds;
376 spectrogram_request sr(sb);
377 time_t row_time;
378
379 sr.sr_width = width;
380 row_time = rounddown(sb.sb_begin_time, this->ss_granularity) +
381 row * this->ss_granularity;
382 sr.sr_begin_time = row_time;
383 sr.sr_end_time = row_time + this->ss_granularity;
384
385 sr.sr_column_size = (sb.sb_max_value_out - sb.sb_min_value_out) /
386 (double) (width - 1);
387
388 spectrogram_row &s_row = this->ss_row_cache[row_time];
389
390 if (s_row.sr_values == nullptr ||
391 s_row.sr_width != width ||
392 s_row.sr_column_size != sr.sr_column_size) {
393 s_row.sr_width = width;
394 s_row.sr_column_size = sr.sr_column_size;
395 delete[] s_row.sr_values;
396 s_row.sr_values = new spectrogram_row::row_bucket[width + 1];
397 this->ss_value_source->spectro_row(sr, s_row);
398 }
399
400 return s_row;
401 }
402