gr-wxgui: adds stripchart trigger mode to graphics sinks
[debian/gnuradio] / gnuradio-core / src / lib / io / gr_oscope_guts.cc
1 /* -*- c++ -*- */
2 /*
3  * Copyright 2003,2005 Free Software Foundation, Inc.
4  * 
5  * This file is part of GNU Radio
6  * 
7  * GNU Radio is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3, or (at your option)
10  * any later version.
11  * 
12  * GNU Radio is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with GNU Radio; see the file COPYING.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 #include <gr_oscope_guts.h>
27 #include <stdexcept>
28 #include <stdio.h>
29 #include <algorithm>
30 #include <unistd.h>
31 #include <math.h>
32 #include <assert.h>
33
34 static const int OUTPUT_RECORD_SIZE = 2048;        // must be power of 2
35
36 static inline int
37 wrap_bi (int buffer_index)                // wrap buffer index
38 {
39   return buffer_index & (OUTPUT_RECORD_SIZE - 1);
40 }
41
42 static inline int
43 incr_bi (int buffer_index)                // increment buffer index
44 {
45   return wrap_bi (buffer_index + 1);
46 }
47
48 static inline int
49 decr_bi (int buffer_index)                // decrement buffer index
50 {
51   return wrap_bi (buffer_index - 1);
52 }
53
54 gr_oscope_guts::gr_oscope_guts (double sample_rate, gr_msg_queue_sptr msgq)
55   : d_nchannels (1),
56     d_msgq (msgq), 
57     d_trigger_mode (gr_TRIG_MODE_AUTO),
58     d_trigger_slope (gr_TRIG_SLOPE_POS),
59     d_trigger_channel (0),
60     d_sample_rate (sample_rate),
61     d_update_rate (20),
62     d_trigger_level (0),
63     d_obi (0),
64     d_state (HOLD_OFF),
65     d_decimator_count (0),
66     d_decimator_count_init (1),
67     d_hold_off_count (0),
68     d_hold_off_count_init (OUTPUT_RECORD_SIZE/2-1),
69     d_pre_trigger_count (0),
70     d_post_trigger_count (0),
71     d_post_trigger_count_init (OUTPUT_RECORD_SIZE/2)
72 {
73   for (int i = 0; i < MAX_CHANNELS; i++)
74     d_buffer[i] = 0;
75
76   for (int i = 0; i < MAX_CHANNELS; i++){
77     d_buffer[i] = new float [OUTPUT_RECORD_SIZE];
78     for (int j = 0; j < OUTPUT_RECORD_SIZE; j++)
79       d_buffer[i][j] = 0.0;
80   }
81
82   // be sure buffer is full before first write
83   enter_hold_off ();
84   update_rate_or_decimation_changed ();
85 }
86
87 gr_oscope_guts::~gr_oscope_guts ()
88 {
89   for (int i = 0; i < MAX_CHANNELS; i++)
90     delete [] d_buffer[i];
91 }
92
93 // MANIPULATORS
94
95 // \p channel_data points to nchannels float values.  These are the values
96 // for each channel at this sample time.
97
98 void
99 gr_oscope_guts::process_sample (const float *channel_data)
100 {
101   d_decimator_count--;
102   if (d_decimator_count > 0)
103     return;
104
105   d_decimator_count = d_decimator_count_init;
106   
107   if (d_trigger_mode != gr_TRIG_MODE_STRIPCHART)
108   {
109           for (int i = 0; i < d_nchannels; i++)
110                 d_buffer[i][d_obi] = channel_data[i];                // copy data into buffer
111
112           switch (d_state){
113           case HOLD_OFF:
114                 d_hold_off_count--;
115                 if (d_hold_off_count <= 0)
116                   enter_look_for_trigger ();
117                 break;
118
119           case LOOK_FOR_TRIGGER:
120                 if (found_trigger ())
121                   enter_post_trigger ();
122                 break;
123
124           case POST_TRIGGER:
125                 d_post_trigger_count--;
126                 if (d_post_trigger_count <= 0){
127                   write_output_records ();
128                   enter_hold_off ();
129                 }
130                 break;
131
132           default:
133                 assert (0);
134           }
135
136           d_obi = incr_bi (d_obi);
137   }
138   else
139   {
140           for (int i = 0; i < d_nchannels; i++)
141           {
142             for (int j = OUTPUT_RECORD_SIZE-1; j >= 0; j--)
143             {
144                         d_buffer[i][j] = d_buffer[i][j-1];
145                 }
146                 d_buffer[i][0] = channel_data[i];
147           }
148           write_output_records();
149   }
150 }
151
152 /*
153  * Functions called on state entry
154  */
155
156 void
157 gr_oscope_guts::enter_hold_off ()
158 {
159   d_state = HOLD_OFF;
160   d_hold_off_count = d_hold_off_count_init;
161 }
162
163 void
164 gr_oscope_guts::enter_look_for_trigger ()
165 {
166   d_pre_trigger_count = 0;
167   d_state = LOOK_FOR_TRIGGER;
168 }
169
170 void
171 gr_oscope_guts::enter_post_trigger ()
172 {
173   d_state = POST_TRIGGER;
174   d_post_trigger_count = d_post_trigger_count_init;
175   //ensure that the trigger offset is no more than than half a sample
176   if (d_trigger_off > .5) d_trigger_off -= 1;
177   else d_post_trigger_count--;
178 }
179
180 // ----------------------------------------------------------------
181 // returns true if trigger found
182
183 bool
184 gr_oscope_guts::found_trigger ()
185 {
186   float prev_sample = d_buffer[d_trigger_channel][decr_bi(d_obi)];
187   float new_sample = d_buffer[d_trigger_channel][d_obi];
188
189   switch (d_trigger_mode){
190
191   case gr_TRIG_MODE_AUTO: //too many samples without a trigger
192     d_pre_trigger_count++;
193     if (d_pre_trigger_count > OUTPUT_RECORD_SIZE/2) return true;
194
195   case gr_TRIG_MODE_NORM: //look for trigger
196     switch (d_trigger_slope){
197
198     case gr_TRIG_SLOPE_POS: //trigger point in pos slope?
199       if (new_sample < d_trigger_level || prev_sample >= d_trigger_level) return false;
200       break;
201
202     case gr_TRIG_SLOPE_NEG: //trigger point in neg slope?
203       if (new_sample > d_trigger_level || prev_sample <= d_trigger_level) return false;
204       break;
205     }
206
207     //calculate the trigger offset in % sample
208     d_trigger_off = (d_trigger_level - prev_sample)/(new_sample - prev_sample);
209     return true;
210
211   case gr_TRIG_MODE_FREE: //free run mode, always trigger
212     d_trigger_off = 0;
213     return true;
214
215   default:
216     assert (0);
217     return false;
218   }
219 }
220
221 // ----------------------------------------------------------------
222 // write output records (duh!)
223
224 void
225 gr_oscope_guts::write_output_records ()
226 {
227   // if the output queue if full, drop the data like its hot.
228   if (d_msgq->full_p())
229     return;
230     // Build a message to hold the output records
231   gr_message_sptr msg = 
232     gr_make_message(0,                                         // msg type
233             d_nchannels,                                       // arg1 for other side
234             OUTPUT_RECORD_SIZE,                                // arg2 for other side
235             ((d_nchannels * OUTPUT_RECORD_SIZE) + 1) * sizeof(float)); // sizeof payload
236
237   float *out = (float *)msg->msg();        // get pointer to raw message buffer
238
239   for (int ch = 0; ch < d_nchannels; ch++){
240     // note that d_obi + 1 points at the oldest sample in the buffer
241     for (int i = 0; i < OUTPUT_RECORD_SIZE; i++){
242       out[i] = d_buffer[ch][wrap_bi(d_obi + 1 + i)];
243     }
244     out += OUTPUT_RECORD_SIZE;
245   }
246   //Set the last sample as the trigger offset:
247   //  The non gl scope sink will not look at this last sample.
248   //  The gl scope sink will use this last sample as an offset.
249   out[0] = d_trigger_off;
250   d_msgq->handle(msg);                // send the msg
251 }
252
253 // ----------------------------------------------------------------
254
255 bool
256 gr_oscope_guts::set_update_rate (double update_rate)
257 {
258   d_update_rate = std::min (std::max (1./10., update_rate), d_sample_rate);
259   update_rate_or_decimation_changed ();
260   return true;
261 }
262
263 bool
264 gr_oscope_guts::set_decimation_count (int decimator_count)
265 {
266   decimator_count = std::max (1, decimator_count);
267   d_decimator_count_init = decimator_count;
268   update_rate_or_decimation_changed ();
269   return true;
270 }
271
272 bool
273 gr_oscope_guts::set_sample_rate(double sample_rate)
274 {
275   d_sample_rate = sample_rate;
276   return set_update_rate(update_rate());
277 }
278
279
280 void
281 gr_oscope_guts::update_rate_or_decimation_changed ()
282 {
283   d_hold_off_count_init =
284     (int) rint (d_sample_rate / d_update_rate / d_decimator_count_init);
285 }
286
287 bool
288 gr_oscope_guts::set_trigger_channel (int channel)
289 {
290   if (channel >= 0 && channel < d_nchannels){
291     d_trigger_channel = channel;
292     trigger_changed ();
293     return true;
294   }
295
296   return false;
297 }
298
299 bool
300 gr_oscope_guts::set_trigger_mode (gr_trigger_mode mode)
301 {
302   d_trigger_mode = mode;
303   trigger_changed ();
304   return true;
305 }
306
307 bool
308 gr_oscope_guts::set_trigger_slope (gr_trigger_slope slope)
309 {
310   d_trigger_slope = slope;
311   trigger_changed ();
312   return true;
313 }
314
315 bool
316 gr_oscope_guts::set_trigger_level (double trigger_level)
317 {
318   d_trigger_level = trigger_level;
319   trigger_changed ();
320   return true;
321 }
322
323 bool
324 gr_oscope_guts::set_trigger_level_auto ()
325 {
326   // find the level 1/2 way between the min and the max
327
328   float min_v = d_buffer[d_trigger_channel][0];
329   float max_v = d_buffer[d_trigger_channel][0];
330
331   for (int i = 1; i < OUTPUT_RECORD_SIZE; i++){
332     min_v = std::min (min_v, d_buffer[d_trigger_channel][i]);
333     max_v = std::max (max_v, d_buffer[d_trigger_channel][i]);
334   }
335   return set_trigger_level((min_v + max_v) * 0.5);
336 }
337
338 bool
339 gr_oscope_guts::set_num_channels(int nchannels)
340 {
341   if (nchannels > 0 && nchannels <= MAX_CHANNELS){
342     d_nchannels = nchannels;
343     return true;
344   }
345   return false;
346 }
347
348
349 void
350 gr_oscope_guts::trigger_changed ()
351 {
352   enter_look_for_trigger ();
353 }
354
355 // ACCESSORS
356
357 int
358 gr_oscope_guts::num_channels () const
359 {
360   return d_nchannels;
361 }
362
363 double
364 gr_oscope_guts::sample_rate () const
365 {
366   return d_sample_rate;
367 }
368
369 double
370 gr_oscope_guts::update_rate () const
371 {
372   return d_update_rate;
373 }
374
375 int
376 gr_oscope_guts::get_decimation_count () const
377 {
378   return d_decimator_count_init;
379 }
380
381 int
382 gr_oscope_guts::get_trigger_channel () const
383 {
384   return d_trigger_channel;
385 }
386
387 gr_trigger_mode
388 gr_oscope_guts::get_trigger_mode () const
389 {
390   return d_trigger_mode;
391 }
392
393 gr_trigger_slope
394 gr_oscope_guts::get_trigger_slope () const
395 {
396   return d_trigger_slope;
397 }
398
399 double
400 gr_oscope_guts::get_trigger_level () const
401 {
402   return d_trigger_level;
403 }
404
405 int
406 gr_oscope_guts::get_samples_per_output_record () const
407 {
408   return OUTPUT_RECORD_SIZE;
409 }