diff options
author | Takamichi Horikawa <takamichiho@gmail.com> | 2017-04-12 01:04:15 +0900 |
---|---|---|
committer | Takamichi Horikawa <takamichiho@gmail.com> | 2017-04-12 01:04:15 +0900 |
commit | ab379f2bb081f3fe2ea77ed163f755f59a49e6cf (patch) | |
tree | b06142d1b4f765e23531abc50ad229494b241a04 /gtk/wavesave.c | |
parent | c6c96944ae1bb1d7363349ec21be9dca76ee9ec4 (diff) |
added wave output
Diffstat (limited to 'gtk/wavesave.c')
-rw-r--r-- | gtk/wavesave.c | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/gtk/wavesave.c b/gtk/wavesave.c new file mode 100644 index 0000000..0af656f --- /dev/null +++ b/gtk/wavesave.c @@ -0,0 +1,256 @@ +#include "wavesave.h" +#include "common/fmplayer_file.h" +#include <sndfile.h> +#include <string.h> +#include "libopna/opna.h" +#include "libopna/opnatimer.h" +#include "common/fmplayer_common.h" + +enum { + SRATE = 55467, +}; + +static void msgbox_err(GtkWindow *parent, const char *msg) { + GtkWidget *d = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + msg); + gtk_dialog_run(GTK_DIALOG(d)); + gtk_widget_destroy(d); +} + +static sf_count_t gio_get_filelen(void *ptr) { + (void)ptr; + return -1; +} + +static sf_count_t gio_seek(sf_count_t offset, int whence, void *ptr) { + GFileOutputStream *os = ptr; + GSeekType st; + switch (whence) { + default: + st = G_SEEK_CUR; + break; + case SEEK_SET: + st = G_SEEK_SET; + break; + case SEEK_END: + st = G_SEEK_END; + break; + } + if (!g_seekable_seek(G_SEEKABLE(os), offset, st, 0, 0)) return -1; + return g_seekable_tell(G_SEEKABLE(os)); +} + +static sf_count_t gio_read(void *buf, sf_count_t count, void *ptr) { + (void)buf; + (void)count; + (void)ptr; + return -1; +} + +static sf_count_t gio_write(const void *buf, sf_count_t count, void *ptr) { + GFileOutputStream *os = ptr; + return g_output_stream_write(G_OUTPUT_STREAM(os), buf, count, 0, 0); +} + +static sf_count_t gio_tell(void *ptr) { + GFileOutputStream *os = ptr; + return g_seekable_tell(G_SEEKABLE(os)); +} + +static SF_VIRTUAL_IO sf_virt_gio = { + .get_filelen = gio_get_filelen, + .seek = gio_seek, + .read = gio_read, + .write = gio_write, + .tell = gio_tell, +}; + +struct fadeout { + struct opna_timer *timer; + struct fmdriver_work *work; + uint64_t vol; + uint8_t loopcnt; +}; + +static bool fadeout_mix( + struct fadeout *fadeout, + int16_t *buf, unsigned frames +) { + opna_timer_mix(fadeout->timer, buf, frames); + for (unsigned i = 0; i < frames; i++) { + int vol = fadeout->vol >> 16; + buf[i*2+0] = (buf[i*2+0] * vol) >> 16; + buf[i*2+1] = (buf[i*2+1] * vol) >> 16; + if (fadeout->work->loop_cnt >= fadeout->loopcnt) { + fadeout->vol = (fadeout->vol * 0xffff0000ull) >> 32; + } + } + return fadeout->vol; +} + +struct thread_write_data { + struct opna opna; + struct opna_timer timer; + struct ppz8 ppz8; + struct fmdriver_work work; + struct fadeout fadeout; + SNDFILE *sndfile; + GtkDialog *dialog; + GtkProgressBar *pbar; + double fraction; + uint8_t adpcm_ram[OPNA_ADPCM_RAM_SIZE]; +}; + +static gboolean idle_close_dialog(gpointer ptr) { + struct thread_write_data *data = ptr; + gtk_dialog_response(data->dialog, GTK_RESPONSE_ACCEPT); + return G_SOURCE_REMOVE; +} + +static gboolean idle_progress_fraction(gpointer ptr) { + struct thread_write_data *data = ptr; + gtk_progress_bar_set_fraction(data->pbar, data->fraction); + return G_SOURCE_REMOVE; +} + +static gpointer thread_write(gpointer ptr) { + struct thread_write_data *data = ptr; + enum { + BUFLEN = 1024, + }; + int16_t buf[BUFLEN*2]; + for (;;) { + memset(buf, 0, sizeof(buf)); + bool end = !fadeout_mix(&data->fadeout, buf, BUFLEN); + if (sf_writef_short(data->sndfile, buf, BUFLEN) != BUFLEN) { + return 0; + } + double newfrac = (double)data->work.timerb_cnt / data->work.loop_timerb_cnt; + if ((newfrac - data->fraction) > 0.005) { + data->fraction = newfrac; + g_idle_add(idle_progress_fraction, ptr); + } + if (end) break; + } + g_idle_add(idle_close_dialog, ptr); + return 0; +} + +static void wavesave(GtkWindow *parent, + struct fmplayer_file *fmfile, + const char *saveuri, + bool flac, + int loopcnt) { + if (loopcnt < 1) loopcnt = 1; + if (loopcnt > 0xff) loopcnt = 0xff; + GFile *savefile = g_file_new_for_uri(saveuri); + GFileOutputStream *os = g_file_replace(savefile, 0, TRUE, 0, 0, 0); + if (!os) { + msgbox_err(parent, "Cannot open output file"); + g_object_unref(savefile); + return; + } + SF_INFO sfinfo = { + .samplerate = SRATE, + .channels = 2, + .format = (flac ? SF_FORMAT_FLAC : SF_FORMAT_WAV | SF_ENDIAN_CPU) | SF_FORMAT_PCM_16, + .seekable = 1, + }; + SNDFILE *sndfile = sf_open_virtual(&sf_virt_gio, SFM_WRITE, &sfinfo, os); + if (!sndfile) { + char *msg = g_strjoin(0, "SNDFILE Error: ", sf_strerror(sndfile), (char *)0); + msgbox_err(parent, msg); + g_free(msg); + g_object_unref(os); + g_object_unref(savefile); + return; + } + struct thread_write_data *tdata = g_new0(struct thread_write_data, 1); + fmplayer_init_work_opna(&tdata->work, &tdata->ppz8, &tdata->opna, &tdata->timer, tdata->adpcm_ram); + fmplayer_file_load(&tdata->work, fmfile, loopcnt); + tdata->fadeout.timer = &tdata->timer; + tdata->fadeout.work = &tdata->work; + tdata->fadeout.vol = 1ull<<32; + tdata->fadeout.loopcnt = loopcnt; + GtkWidget *dialog = gtk_dialog_new_with_buttons("Writing...", + parent, + GTK_DIALOG_MODAL, + "Cancel", + GTK_RESPONSE_CANCEL, + (void*)0); + GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_container_add(GTK_CONTAINER(content), gtk_label_new("Writing...")); + GtkWidget *progress = gtk_progress_bar_new(); + gtk_container_add(GTK_CONTAINER(content), progress); + tdata->sndfile = sndfile; + tdata->dialog = GTK_DIALOG(dialog); + tdata->pbar = GTK_PROGRESS_BAR(progress); + tdata->fraction = 0.0; + GThread *thread = g_thread_new("Write Worker", thread_write, tdata); + gtk_widget_show_all(dialog); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + g_thread_join(thread); + sf_close(sndfile); + g_free(tdata); + g_object_unref(os); + g_object_unref(savefile); +} + +enum { + RESPONSE_IGNORE = 1, +}; + +void wavesave_dialog(GtkWindow *parent, const char *uri) { + GtkWidget *fcd = gtk_file_chooser_dialog_new("Save wave file", + parent, + GTK_FILE_CHOOSER_ACTION_SAVE, + "Cancel", + GTK_RESPONSE_CANCEL, + "Save", + GTK_RESPONSE_ACCEPT, + (void *)0 + ); + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + GtkWidget *typecombo = gtk_combo_box_text_new(); + gtk_widget_set_halign(typecombo, GTK_ALIGN_END); + gtk_box_pack_start(GTK_BOX(hbox), typecombo, FALSE, FALSE, 5); + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(typecombo), 0, "RIFF WAVE (*.wav)"); + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(typecombo), 0, "FLAC (*.flac)"); + gtk_combo_box_set_active(GTK_COMBO_BOX(typecombo), 0); + GtkWidget *loopbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + gtk_widget_set_halign(loopbox, GTK_ALIGN_END); + gtk_box_pack_start(GTK_BOX(hbox), loopbox, FALSE, FALSE, 5); + gtk_box_pack_start(GTK_BOX(loopbox), gtk_label_new("Loop:"), FALSE, FALSE, 5); + GtkWidget *loopspin = gtk_spin_button_new_with_range(1.0, 255.0, 1.0); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(loopspin), 2.0); + gtk_box_pack_start(GTK_BOX(loopbox), loopspin, FALSE, FALSE, 5); + gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(fcd))), + hbox); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(fcd), TRUE); + + char *saveuri = 0; + gtk_widget_show_all(fcd); + gint res; + while ((res = gtk_dialog_run(GTK_DIALOG(fcd))) == RESPONSE_IGNORE); + if (res == GTK_RESPONSE_ACCEPT) { + saveuri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(fcd)); + } + int loopcnt = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(loopspin)); + bool flac = gtk_combo_box_get_active(GTK_COMBO_BOX(typecombo)); + gtk_widget_destroy(fcd); + if (saveuri) { + enum fmplayer_file_error error; + struct fmplayer_file *fmfile = fmplayer_file_alloc(uri, &error); + if (!fmfile) { + char *msg = g_strjoin(0, "Cannot load file: ", fmplayer_file_strerror(error), (char *)0); + msgbox_err(parent, msg); + g_free(msg); + } else { + wavesave(parent, fmfile, saveuri, flac, loopcnt); + fmplayer_file_free(fmfile); + } + } + g_free(saveuri); +} |