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/>.
24 #include <jack/ringbuffer.h>
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
];
59 SNDFILE
*volatile sndfile
;
61 pthread_t thr_writeout
;
62 sem_t sem_sync_completed
;
64 struct recording_buffer
*cur_buffer
;
67 jack_ringbuffer_t
*rb_for_writing
, *rb_just_written
;
70 static void *stream_recorder_thread(void *user_data
)
72 struct stream_recorder
*self
= user_data
;
76 if (!jack_ringbuffer_read(self
->rb_for_writing
, &buf_idx
, 1))
81 if (buf_idx
== STREAM_CMD_QUIT
)
83 if (buf_idx
== STREAM_CMD_SYNC
)
85 // this assumes that the recorder is already detached from any source
86 if (self
->cur_buffer
&& self
->cur_buffer
->write_ptr
)
87 sf_write_float(self
->sndfile
, self
->cur_buffer
->data
, self
->cur_buffer
->write_ptr
);
89 sf_command(self
->sndfile
, SFC_UPDATE_HEADER_NOW
, NULL
, 0);
90 sf_write_sync(self
->sndfile
);
91 sem_post(&self
->sem_sync_completed
);
96 sf_write_float(self
->sndfile
, self
->buffers
[buf_idx
].data
, self
->buffers
[buf_idx
].write_ptr
);
97 self
->buffers
[buf_idx
].write_ptr
= 0;
98 jack_ringbuffer_write(self
->rb_just_written
, &buf_idx
, 1);
99 sf_command(self
->sndfile
, SFC_UPDATE_HEADER_NOW
, NULL
, 0);
105 static gboolean
stream_recorder_attach(struct cbox_recorder
*handler
, struct cbox_recording_source
*src
, GError
**error
)
107 struct stream_recorder
*self
= handler
->user_data
;
112 g_set_error(error
, CBOX_MODULE_ERROR
, CBOX_MODULE_ERROR_FAILED
, "Recorder already attached to a different source");
116 memset(&self
->info
, 0, sizeof(self
->info
));
117 self
->info
.frames
= 0;
118 self
->info
.samplerate
= cbox_rt_get_sample_rate(self
->rt
);
119 self
->info
.channels
= src
->channels
;
120 self
->info
.format
= SF_FORMAT_WAV
| SF_FORMAT_FLOAT
; // XXXKF make format configurable on instantiation
121 self
->info
.sections
= 0;
122 self
->info
.seekable
= 0;
124 self
->sndfile
= sf_open(self
->filename
, SFM_WRITE
, &self
->info
);
128 g_set_error(error
, CBOX_MODULE_ERROR
, CBOX_MODULE_ERROR_FAILED
, "Cannot open sound file '%s': %s", self
->filename
, sf_strerror(NULL
));
132 pthread_create(&self
->thr_writeout
, NULL
, stream_recorder_thread
, self
);
136 void stream_recorder_record_block(struct cbox_recorder
*handler
, const float **buffers
, uint32_t numsamples
)
138 struct stream_recorder
*self
= handler
->user_data
;
143 if (self
->cur_buffer
&& (self
->cur_buffer
->write_ptr
+ numsamples
* self
->info
.channels
) * sizeof(float) >= STREAM_BUFFER_SIZE
)
145 int8_t idx
= self
->cur_buffer
- self
->buffers
;
146 jack_ringbuffer_write(self
->rb_for_writing
, &idx
, 1);
147 self
->cur_buffer
= NULL
;
149 if (!self
->cur_buffer
)
152 if (!jack_ringbuffer_read(self
->rb_just_written
, &buf_idx
, 1)) // underrun
154 self
->cur_buffer
= &self
->buffers
[buf_idx
];
157 unsigned int nc
= self
->info
.channels
;
159 float *wbuf
= self
->cur_buffer
->data
+ self
->cur_buffer
->write_ptr
;
160 for (unsigned int c
= 0; c
< nc
; c
++)
161 for (int i
= 0; i
< numsamples
; i
++)
162 wbuf
[c
+ i
* nc
] = buffers
[c
][i
];
163 self
->cur_buffer
->write_ptr
+= nc
* numsamples
;
166 gboolean
stream_recorder_detach(struct cbox_recorder
*handler
, GError
**error
)
168 struct stream_recorder
*self
= handler
->user_data
;
173 g_set_error(error
, CBOX_MODULE_ERROR
, CBOX_MODULE_ERROR_FAILED
, "No sound file associated with stream recorder");
177 int8_t cmd
= STREAM_CMD_SYNC
;
178 jack_ringbuffer_write(self
->rb_for_writing
, &cmd
, 1);
179 sem_wait(&self
->sem_sync_completed
);
183 void stream_recorder_destroy(struct cbox_recorder
*handler
)
185 struct stream_recorder
*self
= handler
->user_data
;
189 int8_t cmd
= STREAM_CMD_QUIT
;
190 jack_ringbuffer_write(self
->rb_for_writing
, &cmd
, 1);
191 pthread_join(self
->thr_writeout
, NULL
);
198 static gboolean
stream_recorder_process_cmd(struct cbox_command_target
*ct
, struct cbox_command_target
*fb
, struct cbox_osc_command
*cmd
, GError
**error
)
200 struct stream_recorder
*rec
= ct
->user_data
;
201 if (!strcmp(cmd
->command
, "/status") && !strcmp(cmd
->arg_types
, ""))
203 if (!cbox_check_fb_channel(fb
, cmd
->command
, error
))
206 if (!cbox_execute_on(fb
, NULL
, "/filename", "s", error
, rec
->filename
))
208 return CBOX_OBJECT_DEFAULT_STATUS(&rec
->iface
, fb
, error
);
210 return cbox_object_default_process_cmd(ct
, fb
, cmd
, error
);
213 struct cbox_recorder
*cbox_recorder_new_stream(struct cbox_rt
*rt
, const char *filename
)
215 struct stream_recorder
*self
= malloc(sizeof(struct stream_recorder
));
217 CBOX_OBJECT_HEADER_INIT(&self
->iface
, cbox_recorder
, CBOX_GET_DOCUMENT(rt
));
218 cbox_command_target_init(&self
->iface
.cmd_target
, stream_recorder_process_cmd
, self
);
220 self
->iface
.user_data
= self
;
221 self
->iface
.attach
= stream_recorder_attach
;
222 self
->iface
.record_block
= stream_recorder_record_block
;
223 self
->iface
.detach
= stream_recorder_detach
;
224 self
->iface
.destroy
= stream_recorder_destroy
;
226 self
->sndfile
= NULL
;
227 self
->filename
= g_strdup(filename
);
228 self
->cur_buffer
= NULL
;
230 self
->rb_for_writing
= jack_ringbuffer_create(STREAM_BUFFER_COUNT
+ 1);
231 self
->rb_just_written
= jack_ringbuffer_create(STREAM_BUFFER_COUNT
+ 1);
232 sem_init(&self
->sem_sync_completed
, 0, 0);
234 CBOX_OBJECT_REGISTER(&self
->iface
);
236 for (uint8_t i
= 0; i
< STREAM_BUFFER_COUNT
; i
++)
237 jack_ringbuffer_write(self
->rb_just_written
, &i
, 1);