Files: 4be6db6a6163e3a0ccd85e7e193f54406c80a29a / studio.c
6862 bytesRaw
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | struct tune_dl { |
14 | void *handle; |
15 | ino_t inode; |
16 | struct tune *tune; |
17 | }; |
18 | |
19 | static unsigned int rate = 44100; |
20 | static unsigned int channels = 1; |
21 | static unsigned int latency = 500000; /* ring buffer length in us */ |
22 | static snd_pcm_format_t format = SND_PCM_FORMAT_FLOAT; |
23 | static const char *dl_fname = "./tune.so"; |
24 | |
25 | static int tune_dl_load(struct tune_dl *dl, const char *libfname) |
26 | { |
27 | struct stat st; |
28 | if (stat(libfname, &st) < 0) { |
29 | warn("stat"); |
30 | return -1; |
31 | } |
32 | if (dl->inode == st.st_ino) { |
33 | // file unchanged |
34 | return 0; |
35 | } |
36 | // reload the library |
37 | if (dl->handle) { |
38 | dlclose(dl->handle); |
39 | dl->inode = 0; |
40 | } |
41 | dl->handle = dlopen(libfname, RTLD_NOW); |
42 | if (dl->handle == NULL) { |
43 | // warnx("dlopen: %s", dlerror()); |
44 | return 0; |
45 | } |
46 | dl->tune = dlsym(dl->handle, "TUNE"); |
47 | if (dl->tune == NULL) { |
48 | warn("dlsym"); |
49 | dlclose(dl->handle); |
50 | return 0; |
51 | } |
52 | dl->inode = st.st_ino; |
53 | if (dl->tune->reload) { |
54 | dl->tune->reload(dl->tune); |
55 | } |
56 | return 0; |
57 | } |
58 | |
59 | /* |
60 | * Underrun and suspend recovery, from alsa-lib/test/pcm.c |
61 | */ |
62 | |
63 | static int xrun_recovery(snd_pcm_t *handle, int err) |
64 | { |
65 | if (err == -EPIPE) { /* under-run */ |
66 | err = snd_pcm_prepare(handle); |
67 | if (err < 0) |
68 | warnx("Can't recovery from underrun, prepare failed: %s", snd_strerror(err)); |
69 | return 0; |
70 | } else if (err == -ESTRPIPE) { |
71 | while ((err = snd_pcm_resume(handle)) == -EAGAIN) |
72 | sleep(1); /* wait until the suspend flag is released */ |
73 | if (err < 0) { |
74 | err = snd_pcm_prepare(handle); |
75 | if (err < 0) |
76 | warnx("Can't recovery from suspend, prepare failed: %s", snd_strerror(err)); |
77 | } |
78 | return 0; |
79 | } |
80 | return err; |
81 | } |
82 | |
83 | static void generate(struct tune_dl *dl, |
84 | const snd_pcm_channel_area_t *areas, |
85 | snd_pcm_uframes_t offset, |
86 | int count, double *_phase) |
87 | { |
88 | double phase = *_phase; |
89 | double step = 2. * M_PI/(double)rate; |
90 | unsigned char *samples[channels]; |
91 | int steps[channels]; |
92 | unsigned int chn; |
93 | int format_bits = snd_pcm_format_width(format); |
94 | unsigned int maxval = (1 << (format_bits - 1)) - 1; |
95 | int bps = format_bits / 8; /* bytes per sample */ |
96 | int phys_bps = snd_pcm_format_physical_width(format) / 8; |
97 | int big_endian = snd_pcm_format_big_endian(format) == 1; |
98 | int to_unsigned = snd_pcm_format_unsigned(format) == 1; |
99 | |
100 | /* verify and prepare the contents of areas */ |
101 | for (chn = 0; chn < channels; chn++) { |
102 | if ((areas[chn].first % 8) != 0) { |
103 | errx(1, "areas[%i].first == %i, aborting...", chn, areas[chn].first); |
104 | } |
105 | samples[chn] = /*(signed short *)*/(((unsigned char *)areas[chn].addr) + (areas[chn].first / 8)); |
106 | if ((areas[chn].step % 16) != 0) { |
107 | errx(1, "areas[%i].step == %i, aborting...", chn, areas[chn].step); |
108 | } |
109 | steps[chn] = areas[chn].step / 8; |
110 | samples[chn] += offset * steps[chn]; |
111 | } |
112 | |
113 | tune_dl_load(dl, dl_fname); |
114 | |
115 | /* fill the channel areas */ |
116 | while (count-- > 0) { |
117 | union { |
118 | float f; |
119 | int i; |
120 | } fval; |
121 | int res, i; |
122 | if (dl->handle && dl->tune->play) { |
123 | fval.f = dl->tune->play(dl->tune, phase); |
124 | } else { |
125 | fval.f = 0; |
126 | } |
127 | res = fval.i; |
128 | if (to_unsigned) |
129 | res ^= 1U << (format_bits - 1); |
130 | for (chn = 0; chn < channels; chn++) { |
131 | /* Generate data in native endian format */ |
132 | if (big_endian) { |
133 | for (i = 0; i < bps; i++) |
134 | *(samples[chn] + phys_bps - 1 - i) = (res >> i * 8) & 0xff; |
135 | } else { |
136 | for (i = 0; i < bps; i++) |
137 | *(samples[chn] + i) = (res >> i * 8) & 0xff; |
138 | } |
139 | samples[chn] += steps[chn]; |
140 | } |
141 | phase += step; |
142 | /* |
143 | if (phase >= max_phase) |
144 | phase -= max_phase; |
145 | */ |
146 | } |
147 | *_phase = phase; |
148 | } |
149 | |
150 | |
151 | int main(int argc, char *argv[]) |
152 | { |
153 | struct tune_dl dl = {0}; |
154 | int err; |
155 | snd_pcm_t *pcm_out; |
156 | const char *device_out = "default"; |
157 | |
158 | if (argc > 2) { |
159 | device_out = argv[2]; |
160 | } |
161 | |
162 | if (tune_dl_load(&dl, dl_fname) < 0) { |
163 | return 1; |
164 | } |
165 | |
166 | if ((err = snd_pcm_open(&pcm_out, device_out, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { |
167 | warnx("Playback open error: %s", snd_strerror(err)); |
168 | return 1; |
169 | } |
170 | |
171 | if ((err = snd_pcm_set_params(pcm_out, |
172 | format, |
173 | SND_PCM_ACCESS_MMAP_INTERLEAVED, |
174 | channels, |
175 | rate, |
176 | 1, |
177 | latency)) < 0) { |
178 | warnx("Playback open error: %s", snd_strerror(err)); |
179 | return 1; |
180 | } |
181 | |
182 | if (dl.tune->init) { |
183 | dl.tune->init(dl.tune); |
184 | } |
185 | |
186 | snd_pcm_uframes_t buffer_size; |
187 | snd_pcm_uframes_t period_size; |
188 | if ((err = snd_pcm_get_params(pcm_out, &buffer_size, &period_size)) < 0) { |
189 | warnx("Playback get params error: %s", snd_strerror(err)); |
190 | return 1; |
191 | } |
192 | |
193 | double phase = 0; |
194 | const snd_pcm_channel_area_t *my_areas; |
195 | snd_pcm_uframes_t offset, frames, size; |
196 | snd_pcm_sframes_t avail, commitres; |
197 | snd_pcm_state_t state; |
198 | int first = 1; |
199 | |
200 | while (1) { |
201 | state = snd_pcm_state(pcm_out); |
202 | if (state == SND_PCM_STATE_XRUN) { |
203 | err = xrun_recovery(pcm_out, -EPIPE); |
204 | if (err < 0) { |
205 | printf("XRUN recovery failed: %s\n", snd_strerror(err)); |
206 | return err; |
207 | } |
208 | first = 1; |
209 | } else if (state == SND_PCM_STATE_SUSPENDED) { |
210 | err = xrun_recovery(pcm_out, -ESTRPIPE); |
211 | if (err < 0) { |
212 | printf("SUSPEND recovery failed: %s\n", snd_strerror(err)); |
213 | return err; |
214 | } |
215 | } |
216 | avail = snd_pcm_avail_update(pcm_out); |
217 | if (avail < 0) { |
218 | err = xrun_recovery(pcm_out, avail); |
219 | if (err < 0) { |
220 | printf("avail update failed: %s\n", snd_strerror(err)); |
221 | return err; |
222 | } |
223 | first = 1; |
224 | continue; |
225 | } |
226 | if (avail < period_size) { |
227 | if (first) { |
228 | first = 0; |
229 | err = snd_pcm_start(pcm_out); |
230 | if (err < 0) { |
231 | printf("Start error: %s\n", snd_strerror(err)); |
232 | exit(EXIT_FAILURE); |
233 | } |
234 | } else { |
235 | err = snd_pcm_wait(pcm_out, -1); |
236 | if (err < 0) { |
237 | if ((err = xrun_recovery(pcm_out, err)) < 0) { |
238 | printf("snd_pcm_wait error: %s\n", snd_strerror(err)); |
239 | exit(EXIT_FAILURE); |
240 | } |
241 | first = 1; |
242 | } |
243 | } |
244 | continue; |
245 | } |
246 | size = period_size; |
247 | while (size > 0) { |
248 | frames = size; |
249 | err = snd_pcm_mmap_begin(pcm_out, &my_areas, &offset, &frames); |
250 | if (err < 0) { |
251 | if ((err = xrun_recovery(pcm_out, err)) < 0) { |
252 | printf("MMAP begin avail error: %s\n", snd_strerror(err)); |
253 | exit(EXIT_FAILURE); |
254 | } |
255 | first = 1; |
256 | } |
257 | generate(&dl, my_areas, offset, frames, &phase); |
258 | // generate_sine(my_areas, offset, frames, &phase); |
259 | commitres = snd_pcm_mmap_commit(pcm_out, offset, frames); |
260 | if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames) { |
261 | if ((err = xrun_recovery(pcm_out, commitres >= 0 ? -EPIPE : commitres)) < 0) { |
262 | printf("MMAP commit error: %s\n", snd_strerror(err)); |
263 | exit(EXIT_FAILURE); |
264 | } |
265 | first = 1; |
266 | } |
267 | size -= frames; |
268 | } |
269 | |
270 | } |
271 | if (dl.tune->deinit) { |
272 | dl.tune->deinit(dl.tune); |
273 | } |
274 | |
275 | snd_pcm_close(pcm_out); |
276 | } |
277 |
Built with git-ssb-web