1 /* 2 * PCM - Meter level plugin (ncurses) 3 * Copyright (c) 2001 by Abramo Bagnara <abramo (at) alsa-project.org> 4 * 5 * This library is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Lesser General Public License as 7 * published by the Free Software Foundation; either version 2.1 of 8 * the License, or (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 Lesser General Public License for more details. 14 * 15 * You should have received a copy of the GNU Lesser General Public 16 * License along with this library; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 * 19 */ 20 21 #include <curses.h> 22 #include <errno.h> 23 #include <alsa/asoundlib.h> 24 25 #define BAR_WIDTH 70 26 /* milliseconds to go from 32767 to 0 */ 27 #define DECAY_MS 400 28 /* milliseconds for peak to disappear */ 29 #define PEAK_MS 800 30 31 typedef struct _snd_pcm_scope_level_channel { 32 int16_t level; 33 int16_t peak; 34 unsigned int peak_age; 35 } snd_pcm_scope_level_channel_t; 36 37 typedef struct _snd_pcm_scope_level { 38 snd_pcm_t *pcm; 39 snd_pcm_scope_t *s16; 40 snd_pcm_scope_level_channel_t *channels; 41 snd_pcm_uframes_t old; 42 int top; 43 WINDOW *win; 44 unsigned int bar_width; 45 unsigned int decay_ms; 46 unsigned int peak_ms; 47 } snd_pcm_scope_level_t; 48 49 static int level_enable(snd_pcm_scope_t *scope) 50 { 51 snd_pcm_scope_level_t *level = snd_pcm_scope_get_callback_private(scope); 52 int y, x; 53 level->channels = calloc(snd_pcm_meter_get_channels(level->pcm), sizeof(*level->channels)); 54 if (!level->channels) { 55 free(level); 56 return -ENOMEM; 57 } 58 snd_pcm_scope_set_callback_private(scope, level); 59 level->win = initscr(); 60 winsdelln(level->win, snd_pcm_meter_get_channels(level->pcm)); 61 getyx(level->win, y, x); 62 level->top = y; 63 return 0; 64 } 65 66 static void level_disable(snd_pcm_scope_t *scope) 67 { 68 snd_pcm_scope_level_t *level = snd_pcm_scope_get_callback_private(scope); 69 endwin(); 70 free(level->channels); 71 } 72 73 static void level_close(snd_pcm_scope_t *scope) 74 { 75 snd_pcm_scope_level_t *level = snd_pcm_scope_get_callback_private(scope); 76 free(level); 77 } 78 79 static void level_start(snd_pcm_scope_t *scope ATTRIBUTE_UNUSED) 80 { 81 } 82 83 static void level_stop(snd_pcm_scope_t *scope) 84 { 85 snd_pcm_scope_level_t *level = snd_pcm_scope_get_callback_private(scope); 86 unsigned int c; 87 for (c = 0; c < snd_pcm_meter_get_channels(level->pcm); c++) { 88 move(level->top + c, 0); 89 clrtoeol(); 90 } 91 move(level->top, 0); 92 refresh(); 93 } 94 95 static void level_update(snd_pcm_scope_t *scope) 96 { 97 snd_pcm_scope_level_t *level = snd_pcm_scope_get_callback_private(scope); 98 snd_pcm_t *pcm = level->pcm; 99 snd_pcm_sframes_t size; 100 snd_pcm_uframes_t size1, size2; 101 snd_pcm_uframes_t offset, cont; 102 unsigned int c, channels; 103 unsigned int ms; 104 static char bar[256] = { [0 ... 255] = '#' }; 105 int max_decay; 106 size = snd_pcm_meter_get_now(pcm) - level->old; 107 if (size < 0) 108 size += snd_pcm_meter_get_boundary(pcm); 109 offset = level->old % snd_pcm_meter_get_bufsize(pcm); 110 cont = snd_pcm_meter_get_bufsize(pcm) - offset; 111 size1 = size; 112 if (size1 > cont) 113 size1 = cont; 114 size2 = size - size1; 115 ms = size * 1000 / snd_pcm_meter_get_rate(pcm); 116 max_decay = 32768 * ms / level->decay_ms; 117 channels = snd_pcm_meter_get_channels(pcm); 118 for (c = 0; c < channels; c++) { 119 int16_t *ptr; 120 int s, lev = 0; 121 snd_pcm_uframes_t n; 122 snd_pcm_scope_level_channel_t *l; 123 unsigned int lev_pos, peak_pos; 124 l = &level->channels[c]; 125 ptr = snd_pcm_scope_s16_get_channel_buffer(level->s16, c) + offset; 126 for (n = size1; n > 0; n--) { 127 s = *ptr; 128 if (s < 0) 129 s = -s; 130 if (s > lev) 131 lev = s; 132 ptr++; 133 } 134 ptr = snd_pcm_scope_s16_get_channel_buffer(level->s16, c); 135 for (n = size2; n > 0; n--) { 136 s = *ptr; 137 if (s < 0) 138 s = -s; 139 if (s > lev) 140 lev = s; 141 ptr++; 142 } 143 l->level = lev; 144 l->peak_age += ms; 145 if (l->peak_age >= level->peak_ms || 146 lev >= l->peak) { 147 l->peak = lev; 148 l->peak_age = 0; 149 } 150 if (lev < l->level - max_decay) 151 lev = l->level - max_decay; 152 move(level->top + c, 0); 153 lev_pos = lev * level->bar_width / 32768; 154 peak_pos = l->peak * level->bar_width / 32768; 155 addnstr(bar, lev_pos); 156 clrtoeol(); 157 mvaddch(level->top + c, peak_pos - 1, '#'); 158 } 159 move(level->top, 0); 160 refresh(); 161 level->old = snd_pcm_meter_get_now(pcm); 162 } 163 164 static void level_reset(snd_pcm_scope_t *scope) 165 { 166 snd_pcm_scope_level_t *level = snd_pcm_scope_get_callback_private(scope); 167 snd_pcm_t *pcm = level->pcm; 168 memset(level->channels, 0, snd_pcm_meter_get_channels(pcm) * sizeof(*level->channels)); 169 level->old = snd_pcm_meter_get_now(pcm); 170 } 171 172 snd_pcm_scope_ops_t level_ops = { 173 .enable = level_enable, 174 .disable = level_disable, 175 .close = level_close, 176 .start = level_start, 177 .stop = level_stop, 178 .update = level_update, 179 .reset = level_reset, 180 }; 181 182 int snd_pcm_scope_level_open(snd_pcm_t *pcm, const char *name, 183 unsigned int bar_width, unsigned int decay_ms, 184 unsigned int peak_ms, 185 snd_pcm_scope_t **scopep) 186 { 187 snd_pcm_scope_t *scope, *s16; 188 snd_pcm_scope_level_t *level; 189 int err = snd_pcm_scope_malloc(&scope); 190 if (err < 0) 191 return err; 192 level = calloc(1, sizeof(*level)); 193 if (!level) { 194 free(scope); 195 return -ENOMEM; 196 } 197 level->pcm = pcm; 198 level->bar_width = bar_width; 199 level->decay_ms = decay_ms; 200 level->peak_ms = peak_ms; 201 s16 = snd_pcm_meter_search_scope(pcm, "s16"); 202 if (!s16) { 203 err = snd_pcm_scope_s16_open(pcm, "s16", &s16); 204 if (err < 0) { 205 free(scope); 206 free(level); 207 return err; 208 } 209 } 210 level->s16 = s16; 211 snd_pcm_scope_set_ops(scope, &level_ops); 212 snd_pcm_scope_set_callback_private(scope, level); 213 if (name) 214 snd_pcm_scope_set_name(scope, strdup(name)); 215 snd_pcm_meter_add_scope(pcm, scope); 216 *scopep = scope; 217 return 0; 218 } 219 220 int _snd_pcm_scope_level_open(snd_pcm_t *pcm, const char *name, 221 snd_config_t *root, snd_config_t *conf) 222 { 223 snd_config_iterator_t i, next; 224 snd_pcm_scope_t *scope; 225 long bar_width = -1, decay_ms = -1, peak_ms = -1; 226 int err; 227 snd_config_for_each(i, next, conf) { 228 snd_config_t *n = snd_config_iterator_entry(i); 229 const char *id; 230 if (snd_config_get_id(n, &id) < 0) 231 continue; 232 if (strcmp(id, "comment") == 0) 233 continue; 234 if (strcmp(id, "type") == 0) 235 continue; 236 if (strcmp(id, "bar_width") == 0) { 237 err = snd_config_get_integer(n, &bar_width); 238 if (err < 0) { 239 SNDERR("Invalid type for %s", id); 240 return -EINVAL; 241 } 242 continue; 243 } 244 if (strcmp(id, "decay_ms") == 0) { 245 err = snd_config_get_integer(n, &decay_ms); 246 if (err < 0) { 247 SNDERR("Invalid type for %s", id); 248 return -EINVAL; 249 } 250 continue; 251 } 252 if (strcmp(id, "peak_ms") == 0) { 253 err = snd_config_get_integer(n, &peak_ms); 254 if (err < 0) { 255 SNDERR("Invalid type for %s", id); 256 return -EINVAL; 257 } 258 continue; 259 } 260 SNDERR("Unknown field %s", id); 261 return -EINVAL; 262 } 263 if (bar_width < 0) 264 bar_width = BAR_WIDTH; 265 if (decay_ms < 0) 266 decay_ms = DECAY_MS; 267 if (peak_ms < 0) 268 peak_ms = PEAK_MS; 269 return snd_pcm_scope_level_open(pcm, name, bar_width, decay_ms, peak_ms, 270 &scope); 271 } 272