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/>.
25 #include <jack/ringbuffer.h>
28 #include <semaphore.h>
33 // XXXKF the syncing model here is flawed in several ways:
34 // - it's not possible to do block-accurate syncing
35 // - it's not possible to flush the output buffer and stop recording
36 // - rb_for_writing is being written from two threads (audio and UI),
37 // which is not guaranteed to work
40 // 1/8s for 44.1kHz stereo float
41 #define STREAM_BUFFER_SIZE 16384
42 #define STREAM_BUFFER_COUNT 8
44 #define STREAM_CMD_QUIT (-1)
45 #define STREAM_CMD_SYNC (-2)
47 struct recording_buffer
49 float data
[STREAM_BUFFER_SIZE
];
53 struct stream_recorder
55 struct cbox_recorder iface
;
56 struct recording_buffer buffers
[STREAM_BUFFER_COUNT
];
59 struct cbox_engine
*engine
;
61 SNDFILE
*volatile sndfile
;
63 pthread_t thr_writeout
;
64 sem_t sem_sync_completed
;
66 struct recording_buffer
*cur_buffer
;
69 jack_ringbuffer_t
*rb_for_writing
, *rb_just_written
;
72 static void *stream_recorder_thread(void *user_data
)
74 struct stream_recorder
*self
= user_data
;
78 if (!jack_ringbuffer_read(self
->rb_for_writing
, (char *)&buf_idx
, 1))
83 if (buf_idx
== STREAM_CMD_QUIT
)
85 if (buf_idx
== STREAM_CMD_SYNC
)
87 // this assumes that the recorder is already detached from any source
88 if (self
->cur_buffer
&& self
->cur_buffer
->write_ptr
)
89 sf_write_float(self
->sndfile
, self
->cur_buffer
->data
, self
->cur_buffer
->write_ptr
);
91 sf_command(self
->sndfile
, SFC_UPDATE_HEADER_NOW
, NULL
, 0);
92 sf_write_sync(self
->sndfile
);
93 sem_post(&self
->sem_sync_completed
);
98 sf_write_float(self
->sndfile
, self
->buffers
[buf_idx
].data
, self
->buffers
[buf_idx
].write_ptr
);
99 self
->buffers
[buf_idx
].write_ptr
= 0;
100 jack_ringbuffer_write(self
->rb_just_written
, (char *)&buf_idx
, 1);
101 sf_command(self
->sndfile
, SFC_UPDATE_HEADER_NOW
, NULL
, 0);
107 static gboolean
stream_recorder_attach(struct cbox_recorder
*handler
, struct cbox_recording_source
*src
, GError
**error
)
109 struct stream_recorder
*self
= handler
->user_data
;
114 g_set_error(error
, CBOX_MODULE_ERROR
, CBOX_MODULE_ERROR_FAILED
, "Recorder already attached to a different source");
118 memset(&self
->info
, 0, sizeof(self
->info
));
119 self
->info
.frames
= 0;
120 self
->info
.samplerate
= self
->engine
->io_env
.srate
;
121 self
->info
.channels
= src
->channels
;
122 self
->info
.format
= SF_FORMAT_WAV
| SF_FORMAT_FLOAT
; // XXXKF make format configurable on instantiation
123 self
->info
.sections
= 0;
124 self
->info
.seekable
= 0;
126 self
->sndfile
= sf_open(self
->filename
, SFM_WRITE
, &self
->info
);
130 g_set_error(error
, CBOX_MODULE_ERROR
, CBOX_MODULE_ERROR_FAILED
, "Cannot open sound file '%s': %s", self
->filename
, sf_strerror(NULL
));
134 pthread_create(&self
->thr_writeout
, NULL
, stream_recorder_thread
, self
);
138 void stream_recorder_record_block(struct cbox_recorder
*handler
, const float **buffers
, uint32_t numsamples
)
140 struct stream_recorder
*self
= handler
->user_data
;
145 if (self
->cur_buffer
&& (self
->cur_buffer
->write_ptr
+ numsamples
* self
->info
.channels
) * sizeof(float) >= STREAM_BUFFER_SIZE
)
147 int8_t idx
= self
->cur_buffer
- self
->buffers
;
148 jack_ringbuffer_write(self
->rb_for_writing
, (char *)&idx
, 1);
149 self
->cur_buffer
= NULL
;
151 if (!self
->cur_buffer
)
154 if (!jack_ringbuffer_read(self
->rb_just_written
, (char *)&buf_idx
, 1)) // underrun
156 self
->cur_buffer
= &self
->buffers
[buf_idx
];
159 unsigned int nc
= self
->info
.channels
;
161 float *wbuf
= self
->cur_buffer
->data
+ self
->cur_buffer
->write_ptr
;
162 for (unsigned int c
= 0; c
< nc
; c
++)
163 for (int i
= 0; i
< numsamples
; i
++)
164 wbuf
[c
+ i
* nc
] = buffers
[c
][i
];
165 self
->cur_buffer
->write_ptr
+= nc
* numsamples
;
168 gboolean
stream_recorder_detach(struct cbox_recorder
*handler
, GError
**error
)
170 struct stream_recorder
*self
= handler
->user_data
;
175 g_set_error(error
, CBOX_MODULE_ERROR
, CBOX_MODULE_ERROR_FAILED
, "No sound file associated with stream recorder");
179 int8_t cmd
= STREAM_CMD_SYNC
;
180 jack_ringbuffer_write(self
->rb_for_writing
, (char *)&cmd
, 1);
181 sem_wait(&self
->sem_sync_completed
);
185 void stream_recorder_destroy(struct cbox_recorder
*handler
)
187 struct stream_recorder
*self
= handler
->user_data
;
191 int8_t cmd
= STREAM_CMD_QUIT
;
192 jack_ringbuffer_write(self
->rb_for_writing
, (char *)&cmd
, 1);
193 pthread_join(self
->thr_writeout
, NULL
);
200 static gboolean
stream_recorder_process_cmd(struct cbox_command_target
*ct
, struct cbox_command_target
*fb
, struct cbox_osc_command
*cmd
, GError
**error
)
202 struct stream_recorder
*rec
= ct
->user_data
;
203 if (!strcmp(cmd
->command
, "/status") && !strcmp(cmd
->arg_types
, ""))
205 if (!cbox_check_fb_channel(fb
, cmd
->command
, error
))
208 if (!cbox_execute_on(fb
, NULL
, "/filename", "s", error
, rec
->filename
))
210 return CBOX_OBJECT_DEFAULT_STATUS(&rec
->iface
, fb
, error
);
212 return cbox_object_default_process_cmd(ct
, fb
, cmd
, error
);
215 struct cbox_recorder
*cbox_recorder_new_stream(struct cbox_engine
*engine
, struct cbox_rt
*rt
, const char *filename
)
217 struct stream_recorder
*self
= malloc(sizeof(struct stream_recorder
));
219 self
->engine
= engine
;
220 CBOX_OBJECT_HEADER_INIT(&self
->iface
, cbox_recorder
, CBOX_GET_DOCUMENT(engine
));
221 cbox_command_target_init(&self
->iface
.cmd_target
, stream_recorder_process_cmd
, self
);
223 self
->iface
.user_data
= self
;
224 self
->iface
.attach
= stream_recorder_attach
;
225 self
->iface
.record_block
= stream_recorder_record_block
;
226 self
->iface
.detach
= stream_recorder_detach
;
227 self
->iface
.destroy
= stream_recorder_destroy
;
229 self
->sndfile
= NULL
;
230 self
->filename
= g_strdup(filename
);
231 self
->cur_buffer
= NULL
;
233 self
->rb_for_writing
= jack_ringbuffer_create(STREAM_BUFFER_COUNT
+ 1);
234 self
->rb_just_written
= jack_ringbuffer_create(STREAM_BUFFER_COUNT
+ 1);
235 sem_init(&self
->sem_sync_completed
, 0, 0);
237 CBOX_OBJECT_REGISTER(&self
->iface
);
239 for (uint8_t i
= 0; i
< STREAM_BUFFER_COUNT
; i
++)
240 jack_ringbuffer_write(self
->rb_just_written
, (char *)&i
, 1);