1
1
#include < cmath>
2
2
#include < iostream>
3
+ #include < map>
4
+ #include < cstring>
3
5
#include < lsl_cpp.h>
4
6
#include < thread>
7
+ #include < algorithm>
8
+ #include < random>
9
+ #ifndef M_PI
10
+ #define M_PI 3.14159265358979323846
11
+ #endif
5
12
6
13
7
14
// define a packed sample struct (here: a 16 bit stereo sample).
@@ -10,31 +17,128 @@ struct stereo_sample {
10
17
int16_t l, r;
11
18
};
12
19
20
+ struct fake_device {
21
+ /*
22
+ We create a fake device that will generate data. The inner details are not
23
+ so important because typically it will be up to the real data source + SDK
24
+ to provide a way to get data.
25
+ */
26
+ std::size_t n_channels;
27
+ double srate;
28
+ int64_t pattern_samples;
29
+ int64_t head;
30
+ std::vector<int16_t > pattern;
31
+ std::chrono::steady_clock::time_point last_time;
32
+
33
+ fake_device (const int16_t n_channels, const float srate)
34
+ : n_channels(n_channels), srate(srate), head(0 ) {
35
+ pattern_samples = (int64_t )(srate - 0.5 ) + 1 ; // truncate OK.
36
+
37
+ // Pre-allocate entire test pattern. The data _could_ be generated on the fly
38
+ // for a much smaller memory hit, but we also use this example application
39
+ // to test LSL Outlet performance so we want to reduce out-of-LSL CPU
40
+ // utilization.
41
+ int64_t magnitude = std::numeric_limits<int16_t >::max ();
42
+ int64_t offset_0 = magnitude / 2 ;
43
+ int64_t offset_step = magnitude / n_channels;
44
+ pattern.reserve (pattern_samples * n_channels);
45
+ for (auto sample_ix = 0 ; sample_ix < pattern_samples; ++sample_ix) {
46
+ for (auto chan_ix = 0 ; chan_ix < n_channels; ++chan_ix) {
47
+ pattern.emplace_back (
48
+ offset_0 + chan_ix * offset_step +
49
+ magnitude * static_cast <int16_t >(sin (M_PI * chan_ix * sample_ix / n_channels)));
50
+ }
51
+ }
52
+ last_time = std::chrono::steady_clock::now ();
53
+ }
54
+
55
+ std::vector<int16_t > get_data () {
56
+ auto now = std::chrono::steady_clock::now ();
57
+ auto elapsed_nano =
58
+ std::chrono::duration_cast<std::chrono::nanoseconds>(now - last_time).count ();
59
+ std::size_t elapsed_samples = std::size_t (elapsed_nano * srate * 1e-9 ); // truncate OK.
60
+ std::vector<int16_t > result;
61
+ result.resize (elapsed_samples * n_channels);
62
+ int64_t ret_samples = get_data (result);
63
+ std::vector<int16_t > output (result.begin (), result.begin () + ret_samples);
64
+ return output;
65
+ }
66
+
67
+ std::size_t get_data (std::vector<int16_t > &buffer) {
68
+ auto now = std::chrono::steady_clock::now ();
69
+ auto elapsed_nano =
70
+ std::chrono::duration_cast<std::chrono::nanoseconds>(now - last_time).count ();
71
+ int64_t elapsed_samples = std::size_t (elapsed_nano * srate * 1e-9 ); // truncate OK.
72
+ elapsed_samples = std::min (elapsed_samples, (int64_t )buffer.size ());
73
+ if (false ) {
74
+ // The fastest but no patterns.
75
+ memset (&buffer[0 ], 23 , buffer.size () * sizeof buffer[0 ]);
76
+ } else {
77
+ std::size_t end_sample = head + elapsed_samples;
78
+ std::size_t nowrap_samples = std::min (pattern_samples - head, elapsed_samples);
79
+ memcpy (&buffer[0 ], &(pattern[head]), nowrap_samples);
80
+ if (end_sample > pattern_samples) {
81
+ memcpy (&buffer[nowrap_samples], &(pattern[0 ]), elapsed_samples - nowrap_samples);
82
+ }
83
+ }
84
+ head = (head + elapsed_samples) % pattern_samples;
85
+ last_time += std::chrono::nanoseconds (int64_t (1e9 * elapsed_samples / srate));
86
+ return elapsed_samples;
87
+ }
88
+ };
89
+
13
90
int main (int argc, char **argv) {
91
+ std::cout << " SendDataInChunks" << std::endl;
92
+ std::cout << " SendDataInChunks StreamName StreamType samplerate n_channels max_buffered chunk_rate" << std::endl;
93
+ std::cout << " - max_buffered -- duration in sec (or x100 samples if samplerate is 0) to buffer for each outlet" << std::endl;
94
+ std::cout << " - chunk_rate -- number of chunks pushed per second. For this example, make it a common factor of samplingrate and 1000." << std::endl;
95
+
14
96
std::string name{argc > 1 ? argv[1 ] : " MyAudioStream" }, type{argc > 2 ? argv[2 ] : " Audio" };
15
- int samplingrate = argc > 3 ? std::stol (argv[3 ]) : 44100 ;
97
+ int samplingrate = argc > 3 ? std::stol (argv[3 ]) : 44100 ; // Here we specify srate, but typically this would come from the device.
98
+ int n_channels = argc > 4 ? std::stol (argv[4 ]) : 2 ; // Here we specify n_chans, but typically this would come from theh device.
99
+ int32_t max_buffered = argc > 5 ? std::stol (argv[5 ]) : 360 ;
100
+ int32_t chunk_rate = argc > 6 ? std::stol (argv[6 ]) : 10 ; // Chunks per second.
101
+ int32_t chunk_samples = samplingrate > 0 ? std::max ((samplingrate / chunk_rate), 1 ) : 100 ; // Samples per chunk.
102
+ int32_t chunk_duration = 1000 / chunk_rate; // Milliseconds per chunk
103
+
16
104
try {
17
- // make a new stream_info (44.1Khz, 16bit, audio, 2 channels) and open an outlet with it
18
- lsl::stream_info info (name, type, 2 , samplingrate, lsl::cf_int16);
19
- lsl::stream_outlet outlet (info);
105
+ // Prepare the LSL stream.
106
+ lsl::stream_info info (name, type, n_channels, samplingrate, lsl::cf_int16);
107
+ lsl::stream_outlet outlet (info, 0 , max_buffered);
108
+ lsl::xml_element desc = info.desc ();
109
+ desc.append_child_value (" manufacturer" , " LSL" );
110
+ lsl::xml_element chns = desc.append_child (" channels" );
111
+ for (int c = 0 ; c < n_channels; c++) {
112
+ lsl::xml_element chn = chns.append_child (" channel" );
113
+ chn.append_child_value (" label" , " Chan-" + std::to_string (c));
114
+ chn.append_child_value (" unit" , " microvolts" );
115
+ chn.append_child_value (" type" , " EEG" );
116
+ }
117
+
118
+ // Create a connection to our device.
119
+ fake_device my_device (n_channels, (float )samplingrate);
120
+
121
+ // Prepare buffer to get data from 'device'.
122
+ // The buffer should be largery than you think you need. Here we make it twice as large.
123
+ std::vector<int16_t > chunk_buffer (2 * chunk_samples * n_channels);
20
124
21
125
std::cout << " Now sending data..." << std::endl;
126
+
127
+ // Your device might have its own timer. Or you can decide how often to poll
128
+ // your device, as we do here.
22
129
auto nextsample = std::chrono::high_resolution_clock::now ();
23
- std::vector<stereo_sample> mychunk (info.nominal_srate () / 10 );
24
- int phase = 0 ;
130
+ uint64_t sample_counter = 0 ;
25
131
for (unsigned c = 0 ;; c++) {
26
- // wait a bit and generate a chunk of random data
27
- nextsample += std::chrono::milliseconds (100 );
132
+ // wait a bit
133
+ nextsample += std::chrono::milliseconds (chunk_duration );
28
134
std::this_thread::sleep_until (nextsample);
29
135
30
- for (stereo_sample &sample : mychunk) {
31
- sample.l = static_cast <int16_t >(100 * sin (phase / 200 .));
32
- sample.r = static_cast <int16_t >(120 * sin (phase / 400 .));
33
- phase++;
34
- }
136
+ // Get data from device
137
+ std::size_t returned_samples = my_device.get_data (chunk_buffer);
35
138
36
- // send it
37
- outlet.push_chunk_numeric_structs (mychunk);
139
+ // send it to the outlet. push_chunk_multiplexed is one of the more complicated approaches.
140
+ // other push_chunk methods are easier but slightly slower.
141
+ outlet.push_chunk_multiplexed (chunk_buffer.data (), returned_samples * n_channels, 0.0 , true );
38
142
}
39
143
40
144
} catch (std::exception &e) { std::cerr << " Got an exception: " << e.what () << std::endl; }
0 commit comments