]> sigrok.org Git - libsigrok.git/blob - src/output/wavedrom.c
uni-t-ut181a: silence compiler warning, use of uninitialized variable
[libsigrok.git] / src / output / wavedrom.c
1 /*
2  * This file is part of the libsigrok project.
3  *
4  * Copyright (C) 2019 Marc Jacobi <obiwanjacobi@hotmail.com>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <string.h>
22 #include <libsigrok/libsigrok.h>
23 #include "libsigrok-internal.h"
24
25 #define LOG_PREFIX "output/wavedrom"
26
27 struct context {
28         uint32_t channel_count;
29         struct sr_channel **channels;
30         GString **channel_outputs; /* output strings */
31 };
32
33 /* Converts accumulated output data to a JSON string. */
34 static GString *wavedrom_render(const struct context *ctx)
35 {
36         GString *output;
37         size_t ch, i;
38         char last_char, curr_char;
39
40         output = g_string_new("{ \"signal\": [");
41         for (ch = 0; ch < ctx->channel_count; ch++) {
42                 if (!ctx->channel_outputs[ch])
43                         continue;
44
45                 /* Channel strip. */
46                 g_string_append_printf(output,
47                         "{ \"name\": \"%s\", \"wave\": \"", ctx->channels[ch]->name);
48
49                 last_char = 0;
50                 for (i = 0; i < ctx->channel_outputs[ch]->len; i++) {
51                         curr_char = ctx->channel_outputs[ch]->str[i];
52                         /* Data point. */
53                         if (curr_char == last_char) {
54                                 g_string_append_c(output, '.');
55                         } else {
56                                 g_string_append_c(output, curr_char);
57                                 last_char = curr_char;
58                         }
59                 }
60                 if (ch < ctx->channel_count - 1) {
61                         g_string_append(output, "\" },");
62                 } else {
63                         /* Last channel, no comma. */
64                         g_string_append(output, "\" }");
65                 }
66         }
67         g_string_append(output, "], \"config\": { \"skin\": \"narrow\" }}");
68
69         return output;
70 }
71
72 static void process_logic(const struct context *ctx,
73         const struct sr_datafeed_logic *logic)
74 {
75         size_t sample_count, ch, i;
76         uint8_t *sample, bit;
77         GString *accu;
78
79         if (!ctx->channel_count)
80                 return;
81
82         /*
83          * Extract the logic bits for each channel and store them
84          * as wavedrom letters (1/0) in each channel's text string.
85          * This transforms the input which consists of sample sets
86          * that span multiple channels into output stripes per logic
87          * channel which consist of bits for that individual channel.
88          *
89          * TODO Reduce memory consumption during accumulation of
90          * output data.
91          *
92          * Ideally we'd accumulate binary chunks, and defer conversion
93          * to the text format. Analog data already won't get here, only
94          * logic data does. When the per-channel transformation also
95          * gets deferred until later, then the only overhead would be
96          * for disabled logic channels. Which may be acceptable or even
97          * negligable.
98          *
99          * An optional addition to the above mentioned accumulation of
100          * binary data is RLE compression. Mark both the position in the
101          * accumulated data as well as a repetition counter, instead of
102          * repeatedly storing the same sample set. The efficiency of
103          * this approach of course depends on the change rate of input
104          * data. But the approach perfectly matches the WaveDrom syntax
105          * for repeated bit patterns, and thus is easily handled in the
106          * text rendering stage of the output module.
107          */
108         sample_count = logic->length / logic->unitsize;
109         for (i = 0; i < sample_count; i++) {
110                 sample = logic->data + i * logic->unitsize;
111                 for (ch = 0; ch < ctx->channel_count; ch++) {
112                         accu = ctx->channel_outputs[ch];
113                         if (!accu)
114                                 continue;
115                         bit = sample[ch / 8] & (1 << (ch % 8));
116                         g_string_append_c(accu, bit ? '1' : '0');
117                 }
118         }
119 }
120
121 static int receive(const struct sr_output *o,
122         const struct sr_datafeed_packet *packet, GString **out)
123 {
124         struct context *ctx;
125
126         *out = NULL;
127
128         if (!o || !o->sdi || !o->priv)
129                 return SR_ERR_ARG;
130
131         ctx = o->priv;
132
133         switch (packet->type) {
134         case SR_DF_LOGIC:
135                 process_logic(ctx, packet->payload);
136                 break;
137         case SR_DF_END:
138                 *out = wavedrom_render(ctx);
139                 break;
140         }
141
142         return SR_OK;
143 }
144
145 static int init(struct sr_output *o, GHashTable *options)
146 {
147         struct context *ctx;
148         struct sr_channel *channel;
149         GSList *l;
150         size_t i;
151
152         (void)options;
153
154         if (!o || !o->sdi)
155                 return SR_ERR_ARG;
156
157         o->priv = ctx = g_malloc0(sizeof(*ctx));
158
159         ctx->channel_count = g_slist_length(o->sdi->channels);
160         ctx->channels = g_malloc0(
161                 sizeof(ctx->channels[0]) * ctx->channel_count);
162         ctx->channel_outputs = g_malloc0(
163                 sizeof(ctx->channel_outputs[0]) * ctx->channel_count);
164
165         for (i = 0, l = o->sdi->channels; l; l = l->next, i++) {
166                 channel = l->data;
167                 if (channel->enabled && channel->type == SR_CHANNEL_LOGIC) {
168                         ctx->channels[i] = channel;
169                         ctx->channel_outputs[i] = g_string_new(NULL);
170                 }
171         }
172
173         return SR_OK;
174 }
175
176 static int cleanup(struct sr_output *o)
177 {
178         struct context *ctx;
179         GString *s;
180
181         if (!o)
182                 return SR_ERR_ARG;
183
184         ctx = o->priv;
185         o->priv = NULL;
186
187         if (ctx) {
188                 while (--ctx->channel_count) {
189                         s = ctx->channel_outputs[ctx->channel_count];
190                         if (s)
191                                 g_string_free(s, TRUE);
192                 }
193                 g_free(ctx->channel_outputs);
194                 g_free(ctx->channels);
195                 g_free(ctx);
196         }
197
198         return SR_OK;
199 }
200
201 SR_PRIV struct sr_output_module output_wavedrom = {
202         .id = "wavedrom",
203         .name = "WaveDrom",
204         .desc = "WaveDrom.com file format",
205         .exts = (const char *[]){"wavedrom", "json", NULL},
206         .flags = 0,
207         .options = NULL,
208         .init = init,
209         .receive = receive,
210         .cleanup = cleanup,
211 };