diff options
Diffstat (limited to 'gtk')
| -rw-r--r-- | gtk/Makefile.am | 7 | ||||
| -rw-r--r-- | gtk/configure.ac | 2 | ||||
| -rw-r--r-- | gtk/fmplayer.xpm | 26 | ||||
| -rw-r--r-- | gtk/fmplayer32.xpm | 46 | ||||
| -rw-r--r-- | gtk/main.c | 112 | ||||
| -rw-r--r-- | gtk/toneview.c | 2 | ||||
| -rw-r--r-- | gtk/wavesave.c | 256 | ||||
| -rw-r--r-- | gtk/wavesave.h | 8 | 
8 files changed, 372 insertions, 87 deletions
| diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 3524f15..fb06655 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -22,8 +22,8 @@ FMDSP_SRC=../fmdsp/fmdsp.c \  #CFLAGS=  fmplayer_CPPFLAGS=-Wall -Wextra -pedantic \                    -I.. \ -                  $(GTK3_CFLAGS) $(PORTAUDIO_CFLAGS) -fmplayer_LDADD=$(GTK3_LIBS) $(PORTAUDIO_LIBS) +                  $(GTK3_CFLAGS) $(PORTAUDIO_CFLAGS) $(SNDFILE_CFLAGS) +fmplayer_LDADD=$(GTK3_LIBS) $(PORTAUDIO_LIBS) $(SNDFILE_LIBS)  if ENABLE_NEON  LIBOPNA_SRC+=../libopna/opnassg-sinc-neon.s @@ -42,9 +42,12 @@ endif  fmplayer_SOURCES=main.c \                   toneview.c \ +                 wavesave.c \                   ../tonedata/tonedata.c \                   ../common/fmplayer_file.c \                   ../common/fmplayer_file_gio.c \ +                 ../common/fmplayer_work_opna.c \ +                 ../common/fmplayer_drumrom_unix.c \                   $(LIBOPNA_SRC) \                   $(FMDRIVER_SRC) \                   $(FMDSP_SRC) diff --git a/gtk/configure.ac b/gtk/configure.ac index 79d02c1..cab8553 100644 --- a/gtk/configure.ac +++ b/gtk/configure.ac @@ -6,9 +6,9 @@ AC_PROG_RANLIB  AM_PROG_AR  AM_PROG_AS -dnl AM_PATH_SDL2([2.0.5])  PKG_CHECK_MODULES([PORTAUDIO], [portaudio-2.0])  PKG_CHECK_MODULES([GTK3], [gtk+-3.0 cairo]) +PKG_CHECK_MODULES([SNDFILE], [sndfile])  AC_ARG_ENABLE([neon], AS_HELP_STRING([--enable-neon], [Enable NEON optimized functions for SSG sinc filtering and fmdsp palette lookup. Tested on Cortex-A53 (Raspberry PI 3)]))  AM_CONDITIONAL([ENABLE_NEON], [test "x$enable_neon" = "xyes"]) diff --git a/gtk/fmplayer.xpm b/gtk/fmplayer.xpm new file mode 100644 index 0000000..d52d9f4 --- /dev/null +++ b/gtk/fmplayer.xpm @@ -0,0 +1,26 @@ +/* XPM */ +static char *fmplayer_xpm_16[] = { +/* columns rows colors chars-per-pixel */ +"16 16 4 1 ", +"  c #40A040", +". c #C0C0C0", +"X c gray25", +"o c white", +/* pixels */ +"                ", +" .. .. .. .. .. ", +" .. .. .. .. .. ", +"XXXXXXXXXXXXXXXX", +"oXoXoXoXXXXXXXXX", +"oXoXoooXXXXXXXXX", +"XoXXoXoXXXXXXXXX", +"XoXXoXoXXXXXXXXX", +"XoXXoXoXXXXXXXXX", +"XXXXXXXXXXXXXXXX", +"XoXXXooXXoXXXoXX", +"oXoXoXXXoXoXoXoX", +"XXoXooXXoXoXXoXX", +"XoXXoXoXoXoXoXoX", +"oooXXoXXXoXXXoXX", +"XXXXXXXXXXXXXXXX" +}; diff --git a/gtk/fmplayer32.xpm b/gtk/fmplayer32.xpm new file mode 100644 index 0000000..062d12e --- /dev/null +++ b/gtk/fmplayer32.xpm @@ -0,0 +1,46 @@ +/* XPM */ +static char *fmplayer_xpm_32[] = { +/* columns rows colors chars-per-pixel */ +"32 32 8 1 ", +"  c gray25", +". c #606060", +"X c #40A040", +"o c #808080", +"O c #A0A0A0", +"+ c #C0C0C0", +"@ c #D8D8D8", +"# c white", +/* pixels */ +"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", +"OOXOOOXOOOXOOOXOOOXOOOXOOOXOOOXO", +"##X###X###X###X###X###X###X###X#", +"@@X@@@X@@@X@@@X@@@X@@@X@@@X@@@X@", +"++X+++X+++X+++X+++X+++X+++X+++X+", +"................................", +"                                ", +"                                ", +"  #   # #   #                   ", +"  #   # ## ##                   ", +"   # #  # # #                   ", +"    #   #   #                   ", +"    #   #   #                   ", +"    #   #   #                   ", +"    #   #   #                   ", +"    #   #   #                   ", +"                                ", +"   ###   ###   ###   ###        ", +"  #   # #   # #   # #   #       ", +"      # #     #   # #   #       ", +"     #  ####  #   #  ###        ", +"    #   #   # #   # #   #       ", +"   #    #   # #   # #   #       ", +"  #     #   # #   # #   #       ", +"  #####  ###   ###   ###        ", +"                                ", +"                                ", +"++X+++X+++X+++X+++X+++X+++X+++X+", +"++X+++X+++X+++X+++X+++X+++X+++X+", +"OOXOOOXOOOXOOOXOOOXOOOXOOOXOOOXO", +"ooXoooXoooXoooXoooXoooXoooXoooXo", +"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +}; @@ -17,6 +17,11 @@  #include "toneview.h"  #include "oscillo/oscillo.h"  #include "oscilloview.h" +#include "wavesave.h" +#include "common/fmplayer_common.h" + +#include "fmplayer.xpm" +#include "fmplayer32.xpm"  #define DATADIR "/.local/share/fmplayer/"  //#define FMDSP_2X @@ -42,8 +47,6 @@ static struct {    struct ppz8 ppz8;    struct fmdriver_work work;    struct fmdsp fmdsp; -  char drum_rom[OPNA_ROM_SIZE]; -  bool drum_rom_loaded;    char adpcm_ram[OPNA_ADPCM_RAM_SIZE];    struct fmplayer_file *fmfile;    void *data; @@ -80,6 +83,16 @@ static void on_menu_quit(GtkMenuItem *menuitem, gpointer ptr) {    quit();  } +static void on_menu_save(GtkMenuItem *menuitem, gpointer ptr) { +  (void)menuitem; +  (void)ptr; +  if (g.current_uri) { +    char *uri = g_strdup(g.current_uri); +    wavesave_dialog(GTK_WINDOW(g.mainwin), uri); +    g_free(uri); +  } +} +  static void on_tone_view(GtkMenuItem *menuitem, gpointer ptr) {    (void)menuitem;    (void)ptr; @@ -130,67 +143,6 @@ static int pastream_cb(const void *inptr, void *outptr, unsigned long frames,    return paContinue;  } -static void opna_int_cb(void *userptr) { -  struct fmdriver_work *work = (struct fmdriver_work *)userptr; -  work->driver_opna_interrupt(work); -} - -static void opna_mix_cb(void *userptr, int16_t *buf, unsigned samples) { -  struct ppz8 *ppz8 = (struct ppz8 *)userptr; -  ppz8_mix(ppz8, buf, samples); -} - -static void opna_writereg_libopna(struct fmdriver_work *work, unsigned addr, unsigned data) { -  struct opna_timer *timer = (struct opna_timer *)work->opna; -  opna_timer_writereg(timer, addr, data); -} - -static unsigned opna_readreg_libopna(struct fmdriver_work *work, unsigned addr) { -  (void)work; -  //struct opna_timer *timer = (struct opna_timer *)work->opna; -  return opna_readreg(&g.opna, addr); -} - -static uint8_t opna_status_libopna(struct fmdriver_work *work, bool a1) { -  struct opna_timer *timer = (struct opna_timer *)work->opna; -  uint8_t status = opna_timer_status(timer); -  if (!a1) { -    status &= 0x83; -  } -  return status; -} - -static void load_drumrom(void) { -  const char *path = "ym2608_adpcm_rom.bin"; -  const char *home = getenv("HOME"); -  char *dpath = 0; -  if (home) { -    const char *datadir = DATADIR; -    dpath = malloc(strlen(home)+strlen(datadir)+strlen(path) + 1); -    if (dpath) { -      strcpy(dpath, home); -      strcat(dpath, datadir); -      strcat(dpath, path); -      path = dpath; -    } -  } -  FILE *rhythm = fopen(path, "r"); -  free(dpath); -  if (!rhythm) goto err; -  if (fseek(rhythm, 0, SEEK_END) != 0) goto err_file; -  long size = ftell(rhythm); -  if (size != OPNA_ROM_SIZE) goto err_file; -  if (fseek(rhythm, 0, SEEK_SET) != 0) goto err_file; -  if (fread(g.drum_rom, 1, OPNA_ROM_SIZE, rhythm) != OPNA_ROM_SIZE) goto err_file; -  fclose(rhythm); -  g.drum_rom_loaded = true; -  return; -err_file: -  fclose(rhythm); -err: -  return; -} -  static void load_fontrom(void) {    const char *path = "font.rom";    const char *home = getenv("HOME"); @@ -261,24 +213,10 @@ static bool openfile(const char *uri) {    fmplayer_file_free(g.fmfile);    g.fmfile = fmfile;    unsigned mask = opna_get_mask(&g.opna); -  opna_reset(&g.opna); +  g.work = (struct fmdriver_work){0}; +  memset(g.adpcm_ram, 0, sizeof(g.adpcm_ram)); +  fmplayer_init_work_opna(&g.work, &g.ppz8, &g.opna, &g.opna_timer, g.adpcm_ram);    opna_set_mask(&g.opna, mask); -  if (!g.drum_rom_loaded) { -    load_drumrom(); -  } -  if (g.drum_rom_loaded) { -    opna_drum_set_rom(&g.opna.drum, g.drum_rom); -  } -  opna_adpcm_set_ram_256k(&g.opna.adpcm, g.adpcm_ram); -  ppz8_init(&g.ppz8, SRATE, PPZ8MIX); -  opna_timer_reset(&g.opna_timer, &g.opna); -  memset(&g.work, 0, sizeof(g.work)); -  g.work.opna_writereg = opna_writereg_libopna; -  g.work.opna_readreg = opna_readreg_libopna; -  g.work.opna_status = opna_status_libopna; -  g.work.opna = &g.opna_timer; -  g.work.ppz8 = &g.ppz8; -  g.work.ppz8_functbl = &ppz8_functbl;    char *disppath = g_filename_from_uri(uri, 0, 0);    if (disppath) {      strncpy(g.work.filename, disppath, sizeof(g.work.filename)-1); @@ -286,9 +224,7 @@ static bool openfile(const char *uri) {    } else {      strncpy(g.work.filename, uri, sizeof(g.work.filename)-1);    } -  opna_timer_set_int_callback(&g.opna_timer, opna_int_cb, &g.work); -  opna_timer_set_mix_callback(&g.opna_timer, opna_mix_cb, &g.ppz8); -  fmplayer_file_load(&g.work, g.fmfile); +  fmplayer_file_load(&g.work, g.fmfile, 1);    fmdsp_vram_init(&g.fmdsp, &g.work, g.vram);    Pa_StartStream(g.pastream);    g.pa_paused = false; @@ -321,6 +257,9 @@ static GtkWidget *create_menubar() {    GtkWidget *open = gtk_menu_item_new_with_label("Open");    //g_signal_connect(open, "activate", G_CALLBACK(on_menu_open), 0);    gtk_menu_shell_append(GTK_MENU_SHELL(menu), open); +  GtkWidget *save = gtk_menu_item_new_with_label("Save wavefile"); +  g_signal_connect(save, "activate", G_CALLBACK(on_menu_save), 0); +  gtk_menu_shell_append(GTK_MENU_SHELL(menu), save);    GtkWidget *quit = gtk_menu_item_new_with_label("Quit");    g_signal_connect(quit, "activate", G_CALLBACK(on_menu_quit), 0);    gtk_menu_shell_append(GTK_MENU_SHELL(menu), quit); @@ -544,6 +483,13 @@ int main(int argc, char **argv) {  #endif    load_fontrom();    gtk_init(&argc, &argv); +  { +    GList *iconlist = 0; +    iconlist = g_list_append(iconlist, gdk_pixbuf_new_from_xpm_data(fmplayer_xpm_16)); +    iconlist = g_list_append(iconlist, gdk_pixbuf_new_from_xpm_data(fmplayer_xpm_32)); +    gtk_window_set_default_icon_list(iconlist); +    g_list_free(iconlist); +  }    GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL);    g.mainwin = w;    gtk_window_set_resizable(GTK_WINDOW(w), FALSE); diff --git a/gtk/toneview.c b/gtk/toneview.c index 614130b..64c4e98 100644 --- a/gtk/toneview.c +++ b/gtk/toneview.c @@ -42,7 +42,7 @@ gboolean tick_cb(GtkWidget *widget, GdkFrameClock *clock, gpointer ptr) {        tonedata_ch_normalize_tl(&g.tonedata_n.ch[c]);      }      if (g.format != g.format_disp || -        fmplayer_tonedata_channel_isequal(&g.tonedata_n.ch[c], &g.tonedata_n_disp.ch[c])) { +        !fmplayer_tonedata_channel_isequal(&g.tonedata_n.ch[c], &g.tonedata_n_disp.ch[c])) {        g.tonedata_n_disp.ch[c] = g.tonedata_n.ch[c];        tonedata_ch_string(g.format, g.strbuf, &g.tonedata_n.ch[c], 0);        gtk_label_set_text(GTK_LABEL(g.label[c]), g.strbuf); 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); +} diff --git a/gtk/wavesave.h b/gtk/wavesave.h new file mode 100644 index 0000000..7dcea1f --- /dev/null +++ b/gtk/wavesave.h @@ -0,0 +1,8 @@ +#ifndef MYON_FMPLAYER_GTK_WAVESAVE_H_INCLUDED +#define MYON_FMPLAYER_GTK_WAVESAVE_H_INCLUDED + +#include <gtk/gtk.h> + +void wavesave_dialog(GtkWindow *parent, const char *uri); + +#endif // MYON_FMPLAYER_GTK_WAVESAVE_H_INCLUDED | 
