2 Calf Box, an open source musical instrument.
3 Copyright (C) 2010-2011 Krzysztof Foltman
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
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 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 #include <semaphore.h>
32 // XXXKF the syncing model here is flawed in several ways:
33 // - it's not possible to do block-accurate syncing
34 // - it's not possible to flush the output buffer and stop recording
35 // - rb_for_writing is being written from two threads (audio and UI),
36 // which is not guaranteed to work
39 // 1/8s for 44.1kHz stereo float
40 #define STREAM_BUFFER_SIZE 16384
41 #define STREAM_BUFFER_COUNT 8
43 #define STREAM_CMD_QUIT (-1)
44 #define STREAM_CMD_SYNC (-2)
46 struct recording_buffer
48 float data
[STREAM_BUFFER_SIZE
];
52 struct stream_recorder
54 struct cbox_recorder iface
;
55 struct recording_buffer buffers
[STREAM_BUFFER_COUNT
];
58 struct cbox_engine
*engine
;
60 SNDFILE
*volatile sndfile
;
62 pthread_t thr_writeout
;
63 sem_t sem_sync_completed
;
65 struct recording_buffer
*cur_buffer
;
68 struct cbox_fifo
*rb_for_writing
, *rb_just_written
;
71 static void *stream_recorder_thread(void *user_data
)
73 struct stream_recorder
*self
= user_data
;
77 if (!cbox_fifo_read_atomic(self
->rb_for_writing
, &buf_idx
, 1))
82 if (buf_idx
== STREAM_CMD_QUIT
)
84 if (buf_idx
== STREAM_CMD_SYNC
)
86 // this assumes that the recorder is already detached from any source
87 if (self
->cur_buffer
&& self
->cur_buffer
->write_ptr
)
88 sf_write_float(self
->sndfile
, self
->cur_buffer
->data
, self
->cur_buffer
->write_ptr
);
90 sf_command(self
->sndfile
, SFC_UPDATE_HEADER_NOW
, NULL
, 0);
91 sf_write_sync(self
->sndfile
);
92 sem_post(&self
->sem_sync_completed
);
97 sf_write_float(self
->sndfile
, self
->buffers
[buf_idx
].data
, self
->buffers
[buf_idx
].write_ptr
);
98 self
->buffers
[buf_idx
].write_ptr
= 0;
99 cbox_fifo_write_atomic(self
->rb_just_written
, &buf_idx
, 1);
100 sf_command(self
->sndfile
, SFC_UPDATE_HEADER_NOW
, NULL
, 0);
106 static gboolean
stream_recorder_attach(struct cbox_recorder
*handler
, struct cbox_recording_source
*src
, GError
**error
)
108 struct stream_recorder
*self
= handler
->user_data
;
113 g_set_error(error
, CBOX_MODULE_ERROR
, CBOX_MODULE_ERROR_FAILED
, "Recorder already attached to a different source");
117 memset(&self
->info
, 0, sizeof(self
->info
));
118 self
->info
.frames
= 0;
119 self
->info
.samplerate
= self
->engine
->io_env
.srate
;
120 self
->info
.channels
= src
->channels
;
121 self
->info
.format
= SF_FORMAT_WAV
| SF_FORMAT_FLOAT
; // XXXKF make format configurable on instantiation
122 self
->info
.sections
= 0;
123 self
->info
.seekable
= 0;
125 self
->sndfile
= sf_open(self
->filename
, SFM_WRITE
, &self
->info
);
129 g_set_error(error
, CBOX_MODULE_ERROR
, CBOX_MODULE_ERROR_FAILED
, "Cannot open sound file '%s': %s", self
->filename
, sf_strerror(NULL
));
133 pthread_create(&self
->thr_writeout
, NULL
, stream_recorder_thread
, self
);
137 void stream_recorder_record_block(struct cbox_recorder
*handler
, const float **buffers
, uint32_t numsamples
)
139 struct stream_recorder
*self
= handler
->user_data
;
144 if (self
->cur_buffer
&& (self
->cur_buffer
->write_ptr
+ numsamples
* self
->info
.channels
) * sizeof(float) >= STREAM_BUFFER_SIZE
)
146 int8_t idx
= self
->cur_buffer
- self
->buffers
;
147 cbox_fifo_write_atomic(self
->rb_for_writing
, &idx
, 1);
148 self
->cur_buffer
= NULL
;
150 if (!self
->cur_buffer
)
153 if (!cbox_fifo_read_atomic(self
->rb_just_written
, &buf_idx
, 1)) // underrun
155 self
->cur_buffer
= &self
->buffers
[buf_idx
];
158 unsigned int nc
= self
->info
.channels
;
160 float *wbuf
= self
->cur_buffer
->data
+ self
->cur_buffer
->write_ptr
;
161 for (unsigned int c
= 0; c
< nc
; c
++)
162 for (int i
= 0; i
< numsamples
; i
++)
163 wbuf
[c
+ i
* nc
] = buffers
[c
][i
];
164 self
->cur_buffer
->write_ptr
+= nc
* numsamples
;
167 gboolean
stream_recorder_detach(struct cbox_recorder
*handler
, GError
**error
)
169 struct stream_recorder
*self
= handler
->user_data
;
174 g_set_error(error
, CBOX_MODULE_ERROR
, CBOX_MODULE_ERROR_FAILED
, "No sound file associated with stream recorder");
178 int8_t cmd
= STREAM_CMD_SYNC
;
179 cbox_fifo_write_atomic(self
->rb_for_writing
, (char *)&cmd
, 1);
180 sem_wait(&self
->sem_sync_completed
);
184 void stream_recorder_destroy(struct cbox_recorder
*handler
)
186 struct stream_recorder
*self
= handler
->user_data
;
190 int8_t cmd
= STREAM_CMD_QUIT
;
191 cbox_fifo_write_atomic(self
->rb_for_writing
, (char *)&cmd
, 1);
192 pthread_join(self
->thr_writeout
, NULL
);
195 cbox_fifo_destroy(self
->rb_for_writing
);
196 cbox_fifo_destroy(self
->rb_just_written
);
201 static gboolean
stream_recorder_process_cmd(struct cbox_command_target
*ct
, struct cbox_command_target
*fb
, struct cbox_osc_command
*cmd
, GError
**error
)
203 struct stream_recorder
*rec
= ct
->user_data
;
204 if (!strcmp(cmd
->command
, "/status") && !strcmp(cmd
->arg_types
, ""))
206 if (!cbox_check_fb_channel(fb
, cmd
->command
, error
))
209 if (!cbox_execute_on(fb
, NULL
, "/filename", "s", error
, rec
->filename
))
211 return CBOX_OBJECT_DEFAULT_STATUS(&rec
->iface
, fb
, error
);
213 return cbox_object_default_process_cmd(ct
, fb
, cmd
, error
);
216 struct cbox_recorder
*cbox_recorder_new_stream(struct cbox_engine
*engine
, struct cbox_rt
*rt
, const char *filename
)
218 struct stream_recorder
*self
= malloc(sizeof(struct stream_recorder
));
220 self
->engine
= engine
;
221 CBOX_OBJECT_HEADER_INIT(&self
->iface
, cbox_recorder
, CBOX_GET_DOCUMENT(engine
));
222 cbox_command_target_init(&self
->iface
.cmd_target
, stream_recorder_process_cmd
, self
);
224 self
->iface
.user_data
= self
;
225 self
->iface
.attach
= stream_recorder_attach
;
226 self
->iface
.record_block
= stream_recorder_record_block
;
227 self
->iface
.detach
= stream_recorder_detach
;
228 self
->iface
.destroy
= stream_recorder_destroy
;
230 self
->sndfile
= NULL
;
231 self
->filename
= g_strdup(filename
);
232 self
->cur_buffer
= NULL
;
234 self
->rb_for_writing
= cbox_fifo_new(STREAM_BUFFER_COUNT
+ 1);
235 self
->rb_just_written
= cbox_fifo_new(STREAM_BUFFER_COUNT
+ 1);
236 sem_init(&self
->sem_sync_completed
, 0, 0);
238 CBOX_OBJECT_REGISTER(&self
->iface
);
240 for (uint8_t i
= 0; i
< STREAM_BUFFER_COUNT
; i
++)
241 cbox_fifo_write_atomic(self
->rb_just_written
, (char *)&i
, 1);