1 
2 /*
3  * The Real SoundTracker - module info page
4  *
5  * Copyright (C) 1998-2019 Michael Krause
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */
21 
22 #include <gdk/gdkkeysyms.h>
23 #include <glib.h>
24 #include <glib/gi18n.h>
25 #include <glib/gprintf.h>
26 #include <math.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #include "extspinbutton.h"
32 #include "gui-subs.h"
33 #include "gui.h"
34 #include "history.h"
35 #include "instrument-editor.h"
36 #include "keys.h"
37 #include "main.h"
38 #include "module-info.h"
39 #include "sample-editor.h"
40 #include "st-subs.h"
41 #include "track-editor.h"
42 #include "xm.h"
43 
44 static GtkWidget *ilist, *slist, *ilist2, *slist2, *songname, *sw1, *sw2;
45 static GtkWidget *freqmode_w[2], *ptmode_toggle;
46 static GtkWidget *ins1_label, *smp1_label, *ins2_label, *smp2_label, *mix_label1, *mix_label2;
47 static guint curi = 0, curs = 0, desti = 0, dests = 0, mix_prev_note;
48 static gfloat mixlevel = 50.0;
49 static gboolean is_tuning = FALSE;
50 
51 struct tuner {
52     GtkWidget *frame, *note_label, *note2_label, *note_spin;
53     GtkWidget *period_label, *period_label2, *period_spin, *play, *stop;
54     guint note, mode, period;
55     gboolean is_ref;
56     gint timer;
57     const gchar *label_text;
58 };
59 
60 static struct tuner *tuning, *ref;
61 
62 static STSample prev_sample = {{0}};
63 static guint32 prev_alloc_length = 0;
64 
65 static const gchar* mode1[] = {N_("Single"), N_("Cyclic"), N_("Keyboard")};
66 static const gchar* mode2[] = {N_("Single"), N_("Cyclic"), N_("Coupled")};
67 enum {
68     MODE_SINGLE = 0,
69     MODE_CYCLIC,
70     MODE_KEYBOARD,
71     MODE_COUPLED = MODE_KEYBOARD,
72     MODE_LAST
73 };
74 
75 static void
76 ptmode_changed(GtkToggleButton* widget)
77 {
78     if (xm) {
79         history_log_toggle_button(widget, _("Protracker mode changing"),
80             HISTORY_FLAG_LOG_PAGE, xm->flags & XM_FLAGS_IS_MOD);
81         if (gtk_toggle_button_get_active(widget))
82             xm->flags |= XM_FLAGS_IS_MOD;
83         else
84             xm->flags &= ~XM_FLAGS_IS_MOD;
85     }
86 }
87 
88 static void
89 freqmode_changed(GtkToggleButton* widget)
90 {
91     if (xm && gtk_toggle_button_get_active(widget)) {
92         gint m = find_current_toggle(freqmode_w, 2);
93 
94         history_log_radio_group(freqmode_w, _("Frequency mode changing"),
95             HISTORY_FLAG_LOG_PAGE, (xm->flags & XM_FLAGS_AMIGA_FREQ) != 0 ? 1 : 0, 2);
96         if (m)
97             xm->flags |= XM_FLAGS_AMIGA_FREQ;
98         else
99             xm->flags &= ~XM_FLAGS_AMIGA_FREQ;
100     }
101 }
102 
103 static void
104 songname_changed(GtkEntry* entry)
105 {
106     gchar* term;
107 
108     history_log_entry(entry, _("Song name changing"), 20 * 4,
109         HISTORY_FLAG_LOG_PAGE, xm->utf_name);
110     g_utf8_strncpy(xm->utf_name, gtk_entry_get_text(entry), 20);
111     term = g_utf8_offset_to_pointer(xm->utf_name, 21);
112     term[0] = 0;
113     xm->needs_conversion = TRUE;
114 }
115 
116 static void
117 modinfo_update_all_samples(const gint n, const gboolean second)
118 {
119     guint i;
120     GtkTreeModel* model;
121     GtkWidget* list = second ? slist2 : slist;
122     GtkListStore* slist_store = GUI_GET_LIST_STORE(list);
123 
124     model = gui_list_freeze(list);
125     for (i = 0; i < ST_NUM_SAMPLES(&xm->instruments[n]); i++) {
126         GtkTreeIter iter;
127 
128         if (!gui_list_get_iter(i, slist_store, &iter))
129             return; /* Some bullshit happens :-/ */
130         gtk_list_store_set(slist_store, &iter, 1,
131             xm->instruments[n].samples[i].utf_name, -1);
132     }
133     gui_list_thaw(list, model);
134 }
135 
136 static void
137 modinfo_update_instrument_full(const gint n, const gboolean second)
138 {
139     GtkTreeIter iter;
140     GtkListStore* list_store = GUI_GET_LIST_STORE(second ? ilist2 : ilist);
141 
142     if (!gui_list_get_iter(n, list_store, &iter))
143         return; /* Some bullshit happens :-/ */
144     gtk_list_store_set(list_store, &iter, 1, xm->instruments[n].utf_name,
145         2, st_instrument_num_samples(&xm->instruments[n]), -1);
146 
147     if (n == (second ? desti : curi))
148         modinfo_update_all_samples(n, second);
149 }
150 
151 static void
152 update_labels(struct tuner* t,
153     GtkWidget *label,
154     GtkWidget *mix_label,
155     const gint n,
156     const gint ins,
157     const gint smp)
158 {
159     gchar *buf;
160 
161     buf = g_strdup_printf("%i", n);
162     gtk_label_set_text(GTK_LABEL(label), buf);
163     g_free(buf);
164     buf = g_strdup_printf(_("%sIns. %i, Smp. %i"), t->label_text, ins + 1, smp);
165     gtk_frame_set_label(GTK_FRAME(t->frame), buf);
166     g_free(buf);
167     buf = g_strdup_printf(_("I%i, S%i"), ins + 1, smp);
168     gtk_label_set_text(GTK_LABEL(mix_label), buf);
169     g_free(buf);
170 }
171 
172 static void
173 ilist_select(GtkTreeSelection* sel)
174 {
175     gint row = gui_list_get_selection_index(sel);
176 
177     if (row != -1 && row != curi) {
178         curi = row;
179         gui_set_current_instrument(row + 1);
180         update_labels(tuning, ins1_label, mix_label1, row + 1, curi, curs);
181     }
182 }
183 
184 static void
185 slist_select(GtkTreeSelection* sel)
186 {
187     gint row = gui_list_get_selection_index(sel);
188 
189     if (row != -1 && row != curs) {
190         curs = row;
191         gui_set_current_sample(row);
192         update_labels(tuning, smp1_label, mix_label1, row, curi, curs);
193     }
194 }
195 
196 static void
197 ilist2_select(GtkTreeSelection* sel)
198 {
199     gint row = gui_list_get_selection_index(sel);
200 
201     if (row != -1 && row != desti) {
202         desti = row;
203         modinfo_update_all_samples(row, TRUE);
204         update_labels(ref, ins2_label, mix_label2, row + 1, desti, dests);
205     }
206 }
207 
208 static void
209 slist2_select(GtkTreeSelection* sel)
210 {
211     gint row = gui_list_get_selection_index(sel);
212 
213     if (row != -1 && row != dests) {
214         dests = row;
215         update_labels(ref, smp2_label, mix_label2, row, desti, dests);
216     }
217 }
218 
219 static GtkWidget*
220 add_label(const gchar* title,
221     const gchar* value,
222     const GtkWidget* box)
223 {
224     GtkWidget *box2, *thing, *frame;
225 
226     box2 = gtk_hbox_new(FALSE, 4);
227     gtk_box_pack_start(GTK_BOX(box), box2, FALSE, FALSE, 0);
228 
229     thing = gtk_label_new(_(title));
230     gtk_box_pack_start(GTK_BOX(box2), thing, FALSE, FALSE, 0);
231 
232     frame = gtk_frame_new(NULL);
233     gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
234     gtk_box_pack_end(GTK_BOX(box2), frame, FALSE, FALSE, 0);
235     gtk_widget_show(frame);
236 
237     thing = gtk_label_new(value);
238     gtk_misc_set_alignment(GTK_MISC(thing), 1.0, 0.5);
239     gtk_container_add(GTK_CONTAINER(frame), thing);
240 
241     return thing;
242 }
243 
244 static void
245 exp_change(GtkExpander *expndr)
246 {
247     is_tuning = gtk_expander_get_expanded(expndr);
248 
249     (is_tuning ? gtk_widget_show : gtk_widget_hide)(sw1);
250     (is_tuning ? gtk_widget_show : gtk_widget_hide)(sw2);
251     if (!is_tuning) {
252         modinfo_stop_cycling();
253         gui_play_stop();
254     }
255 }
256 
257 static void
258 modinfo_xcopy_ins(const gpointer data)
259 {
260     if (curi != desti) {
261         gint action = GPOINTER_TO_INT(data);
262         STInstrument* src_ins = &xm->instruments[curi];
263         STInstrument* dst_ins = &xm->instruments[desti];
264 
265         /* It's simplier just to stop playing instead of performing some doubtful locks */
266         gui_play_stop();
267 
268         instrument_editor_xcopy_instruments(src_ins, dst_ins, action);
269         if (action) {
270             instrument_editor_update(FALSE);
271             sample_editor_update();
272             modinfo_update_instrument(curi);
273         }
274         modinfo_update_instrument(desti);
275     }
276 }
277 
278 static
279 void modinfo_update_sample_full(const gint ins, const gint smp, const gboolean second)
280 {
281     GtkTreeIter iter;
282     GtkListStore* list_store = GUI_GET_LIST_STORE(second ? slist2 : slist);
283 
284     if (!gui_list_get_iter(smp, list_store, &iter))
285         return; /* Some bullshit happens :-/ */
286     gtk_list_store_set(list_store, &iter, 1,
287         xm->instruments[ins].samples[smp].utf_name, -1);
288 }
289 
290 /* Not a simple case. We have to back up both a sample and the
291    sample map of an instrument using one argument */
292 struct Smp_n_MapBackup {
293     gint ins, smp; /* Destination instrument and sample */
294     gint8 samplemap[96];
295     STSample sample;
296     gint16 data[1];
297 };
298 
299 static struct Smp_n_MapBackup*
300 make_sample_copy_arg(const gint ins, const gint smp, const guint32 backup_size)
301 {
302     struct Smp_n_MapBackup* arg;
303     STInstrument* src_ins = &xm->instruments[ins];
304     STSample* src_smp = &src_ins->samples[smp];
305 
306     arg = g_malloc(backup_size);
307     if (!arg) {
308         gui_oom_error();
309         return NULL;
310     }
311 
312     arg->ins = ins;
313     arg->smp = smp;
314     memcpy(arg->samplemap, src_ins->samplemap, sizeof(arg->samplemap));
315     arg->sample = *src_smp;
316     if (src_smp->sample.length)
317         memcpy(arg->data, src_smp->sample.data, src_smp->sample.length * sizeof(arg->data[0]));
318 
319     return arg;
320 }
321 
322 static void sample_copy_undo(const gint ins, const gint smp, const gboolean redo,
323     gpointer arg, gpointer data)
324 {
325     struct Smp_n_MapBackup* bup = arg;
326     STInstrument* dst_ins = &xm->instruments[bup->ins];
327     STSample* dst_smp = &dst_ins->samples[bup->smp];
328     gint8* tmp_smap = alloca(sizeof(bup->samplemap));
329     const gsize data_length = dst_smp->sample.length * sizeof(dst_smp->sample.data[0]);
330     gint16* tmp_data = NULL;
331     STSample tmp_smp;
332     GMutex tmp_lock;
333 
334     if (data_length) {
335         tmp_data = g_malloc(data_length);
336         if (!tmp_data) {
337             gui_oom_error();
338             return;
339         }
340         memcpy(tmp_data, dst_smp->sample.data, data_length);
341     }
342     tmp_smp = *dst_smp;
343     memcpy(tmp_smap, dst_ins->samplemap, sizeof(dst_ins->samplemap));
344 
345     g_mutex_lock(&dst_smp->sample.lock);
346     tmp_lock = dst_smp->sample.lock;
347     if (dst_smp->sample.length && dst_smp->sample.data)
348         g_free(dst_smp->sample.data);
349     memcpy(dst_ins->samplemap, bup->samplemap, sizeof(dst_ins->samplemap));
350     *dst_smp = bup->sample;
351     if (dst_smp->sample.length) {
352         dst_smp->sample.data = g_new(gint16, dst_smp->sample.length);
353         memcpy(dst_smp->sample.data, bup->data, dst_smp->sample.length * sizeof(bup->data[0]));
354     } else
355         dst_smp->sample.data = NULL;
356     dst_smp->sample.lock = tmp_lock;
357     g_mutex_unlock(&dst_smp->sample.lock);
358 
359     bup->sample = tmp_smp;
360     if (tmp_data) {
361         memcpy(bup->data, tmp_data, bup->sample.sample.length * sizeof(tmp_data[0]));
362         g_free(tmp_data);
363     }
364     memcpy(bup->samplemap, tmp_smap, sizeof(bup->samplemap));
365 
366     modinfo_update_instrument(bup->ins);
367 }
368 
369 static void
370 sample_xchg_undo(const gint ins, const gint smp, const gboolean redo,
371     gpointer arg, gpointer data)
372 {
373     sample_editor_xcopy_samples(arg, data, TRUE);
374     modinfo_update_instrument_full(ins - 1, FALSE);
375     modinfo_update_instrument_full(desti, TRUE);
376 }
377 
378 static void
379 modinfo_xcopy_smp(const gpointer data)
380 {
381     if (curi != desti || curs != dests) {
382         gint action = GPOINTER_TO_INT(data);
383         STInstrument* dest_ins = &xm->instruments[desti];
384         STInstrument* src_ins = &xm->instruments[curi];
385         STSample* dest_smp = &dest_ins->samples[dests];
386         STSample* src_smp = &src_ins->samples[curs];
387 
388         if (action) {
389             history_log_action(HISTORY_ACTION_POINTER_NOFREE, _("Sample exchanging"),
390                 HISTORY_FLAG_LOG_ALL, sample_xchg_undo, src_smp, 0, dest_smp);
391         } else {
392             struct Smp_n_MapBackup* arg;
393             const guint32 data_length = MAX(src_smp->sample.length, dest_smp->sample.length);
394             const guint32 backup_size = sizeof(struct Smp_n_MapBackup) +
395                 sizeof(arg->data[0]) * (data_length - 1);
396 
397             if (history_check_size(backup_size)) {
398                 arg = make_sample_copy_arg(desti, dests, backup_size);
399                 history_log_action(HISTORY_ACTION_POINTER, _("Sample overwriting"),
400                     HISTORY_FLAG_LOG_ALL, sample_copy_undo, NULL, backup_size, arg);
401             } else if (!history_query_oversized(mainwindow))
402                 return;
403         }
404 
405         if (st_instrument_num_samples(dest_ins) == 0) {
406             st_clean_instrument_full(dest_ins, NULL, FALSE);
407             memset(dest_ins->samplemap, dests, sizeof(dest_ins->samplemap));
408         }
409         if (action && st_instrument_num_samples(src_ins) == 0) {
410             st_clean_instrument_full(src_ins, NULL, FALSE);
411             memset(src_ins->samplemap, curs, sizeof(src_ins->samplemap));
412         }
413         sample_editor_xcopy_samples(src_smp, dest_smp, action);
414 
415         if (action) {
416             sample_editor_update();
417             instrument_editor_update(TRUE);
418         }
419         modinfo_update_instrument(desti);
420         if (curi == desti)
421             modinfo_update_sample_full(desti, dests, FALSE);
422     }
423 }
424 
425 static void
426 put_labelled_spin_button(const gchar* title, GtkAdjustment* adj, GtkWidget* box)
427 {
428     GtkWidget *hbox, *thing;
429 
430     hbox = gtk_hbox_new(FALSE, 4);
431     gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
432     thing = gtk_label_new(title);
433     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
434     thing = extspinbutton_new(adj, 0, 0, TRUE);
435     gtk_box_pack_end(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
436 }
437 
438 static void
439 note_changed(GtkSpinButton* spin, struct tuner* t)
440 {
441     t->note = gtk_spin_button_get_value_as_int(spin);
442     gtk_label_set_text(GTK_LABEL(t->note_label), tracker_get_note_name(t->note));
443 }
444 
445 static void
446 mix_prev_note_changed(GtkSpinButton* spin, GtkLabel* label)
447 {
448     mix_prev_note = gtk_spin_button_get_value_as_int(spin);
449     gtk_label_set_text(label, tracker_get_note_name(mix_prev_note));
450 }
451 
452 static void
453 period_changed(GtkSpinButton* spin, struct tuner* t)
454 {
455     t->period = lrint(gtk_spin_button_get_value(spin) * 1000.0);
456 }
457 
458 static void
459 mode_changed(GtkComboBox* combo, struct tuner* t)
460 {
461     gboolean active;
462 
463     t->mode = gtk_combo_box_get_active(combo);
464 
465     active = (t->mode == MODE_CYCLIC);
466     gtk_widget_set_sensitive(t->period_label, active);
467     gtk_widget_set_sensitive(t->period_spin, active);
468     gtk_widget_set_sensitive(t->period_label2, active);
469 
470     active = !(t->mode == MODE_KEYBOARD); /* == MODE_COUPLED also */
471     gtk_widget_set_sensitive(t->play, active);
472     gtk_widget_set_sensitive(t->stop, active);
473     if (!t->is_ref) {
474         gtk_widget_set_sensitive(t->note2_label, active);
475         gtk_widget_set_sensitive(t->note_spin, active);
476     }
477 }
478 
479 static gboolean
480 play_note(gpointer data)
481 {
482     struct tuner* t = (struct tuner*)data;
483 
484     gint ins_num = t->is_ref ? desti : curi;
485     gint smp_num = t->is_ref ? dests : curs;
486     STSample* sample = &xm->instruments[ins_num].samples[smp_num];
487 
488     if (!sample) {
489         t->timer = -1;
490         return FALSE;
491     }
492 
493     gui_play_note_full(t->is_ref ? 0 : 1, t->note + 1, sample, 0, sample->sample.length, TRUE);
494     if (!t->is_ref && ref->mode == MODE_COUPLED) {
495         sample = &xm->instruments[desti].samples[dests];
496         if (sample)
497             gui_play_note_full(0, ref->note + 1, sample, 0, sample->sample.length, FALSE);
498     }
499 
500     return TRUE;
501 }
502 
503 static void
504 play_clicked(struct tuner* t)
505 {
506     switch (t->mode) {
507     case MODE_SINGLE:
508         play_note(t);
509         break;
510     case MODE_CYCLIC:
511         if (t->timer == -1) {
512             play_note(t);
513             t->timer = g_timeout_add(t->period, play_note, t);
514         }
515         break;
516     default:
517         break;
518     }
519 }
520 
521 static void
522 stop_clicked(struct tuner* t)
523 {
524     if (t->timer != -1) {
525         g_source_remove(t->timer);
526         t->timer = -1;
527     }
528     gui_stop_note(t->is_ref ? 0 : 1);
529     if (!t->is_ref && ref->mode == MODE_COUPLED)
530         gui_stop_note(0);
531 }
532 
533 static struct tuner*
534 tuner_new(const gboolean is_ref, const gchar* label_text)
535 {
536     struct tuner* t = g_new(struct tuner, 1);
537 
538     t->note = 48;
539     t->mode = 0;
540     t->period = 1000;
541     t->is_ref = is_ref;
542     t->timer = -1;
543     t->label_text = label_text;
544 
545     t->frame = gtk_frame_new("");
546     gtk_frame_set_shadow_type(GTK_FRAME(t->frame), GTK_SHADOW_IN);
547 
548     return t;
549 }
550 
551 static GtkWidget*
552 tuner_populate(struct tuner* t)
553 {
554     GtkWidget *vbox, *hbox, *thing;
555     GtkListStore* list_store;
556     GtkTreeIter iter;
557     gint i, width, height;
558 
559     vbox = gtk_vbox_new(TRUE, 2);
560     hbox = gtk_hbox_new(FALSE, 4);
561     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
562 
563     t->play = thing = gtk_button_new();
564     gtk_container_add(GTK_CONTAINER(thing), gui_get_pixmap(GUI_PIXMAP_PLAY));
565     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
566     g_signal_connect_swapped(thing, "clicked", G_CALLBACK(play_clicked), t);
567 
568     t->stop = thing = gtk_button_new();
569     gtk_container_add(GTK_CONTAINER(thing), gui_get_pixmap(GUI_PIXMAP_STOP));
570     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
571     g_signal_connect_swapped(thing, "clicked", G_CALLBACK(stop_clicked), t);
572 
573     t->period_label = thing = gtk_label_new(_("Period"));
574     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
575     gtk_widget_set_sensitive(thing, FALSE);
576     t->period_label2 = thing = gtk_label_new(_("s"));
577     gtk_box_pack_end(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
578     gtk_widget_set_sensitive(thing, FALSE);
579     t->period_spin = thing = extspinbutton_new(
580         GTK_ADJUSTMENT(gtk_adjustment_new(1.0, 0.0, 10.0, 0.1, 1.0, 0.0)),
581         0.0, 1, TRUE);
582     gtk_box_pack_end(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
583     gtk_widget_set_sensitive(thing, FALSE);
584     g_signal_connect(thing, "value-changed", G_CALLBACK(period_changed), t);
585 
586     hbox = gtk_hbox_new(FALSE, 4);
587     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
588     thing = gtk_hbox_new(FALSE, 4);
589     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
590 
591     thing = gtk_label_new(_("Mode"));
592     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
593 
594     list_store = gtk_list_store_new(1, G_TYPE_STRING);
595     for (i = 0; i < MODE_LAST; i++) {
596         gtk_list_store_append(list_store, &iter);
597         gtk_list_store_set(list_store, &iter, 0, _((t->is_ref ? mode2 : mode1)[i]), -1);
598     }
599     thing = gui_combo_new(list_store);
600     gtk_combo_box_set_active(GTK_COMBO_BOX(thing), 0);
601     gtk_box_pack_end(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
602     g_signal_connect(thing, "changed", G_CALLBACK(mode_changed), t);
603 
604     hbox = gtk_hbox_new(FALSE, 4);
605     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
606     thing = gtk_hbox_new(FALSE, 4);
607     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
608 
609     t->note2_label = thing = gtk_label_new(_("Note"));
610     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
611 
612     thing = gtk_frame_new(NULL);
613     gtk_frame_set_shadow_type(GTK_FRAME(thing), GTK_SHADOW_IN);
614     gtk_box_pack_end(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
615     t->note_label = gtk_label_new("");
616     gui_get_pixel_size(t->note_label, " C#4 ", &width, &height);
617     gtk_widget_set_size_request(t->note_label, width, -1);
618     gtk_container_add(GTK_CONTAINER(thing), t->note_label);
619 
620     t->note_spin = thing = extspinbutton_new(
621         GTK_ADJUSTMENT(gtk_adjustment_new(48.0, 0.0, NUM_NOTES - 1, 1.0, 5.0, 0.0)),
622         0.0, 0, TRUE);
623     gtk_box_pack_end(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
624     g_signal_connect(thing, "value-changed", G_CALLBACK(note_changed), t);
625     note_changed(GTK_SPIN_BUTTON(thing), t);
626 
627     return vbox;
628 }
629 
630 static void
631 mixlevel_changed(GtkAdjustment* adj)
632 {
633     mixlevel = gtk_adjustment_get_value(adj);
634 }
635 
636 static void
637 same_sample_warning(void)
638 {
639     static GtkWidget* dialog = NULL;
640 
641     gui_warning_dialog(&dialog, _("Mixing a sample with itself is not a useful idea..."), FALSE);
642 }
643 
644 static void
645 mix_samples(const STSample* dst,
646     STSample* sample,
647     STSample* sample1,
648     const guint32 length1,
649     const guint32 length2,
650     gfloat mlevel)
651 {
652     guint32 i;
653     STSample *rest;
654 
655     for (i = 0; i < MIN(length1, length2); i++)
656         dst->sample.data[i] = lrintf((float)sample->sample.data[i] * mlevel / 100.0 +
657             (float)sample1->sample.data[i] * (100.0 - mlevel) / 100.0);
658 
659     if (length1 < length2) {
660         mlevel = 100.0 - mlevel;
661         rest = sample1;
662     } else
663         rest = sample;
664 
665     for (; i < MAX(length1, length2); i++)
666         dst->sample.data[i] = lrintf((float)rest->sample.data[i] * mlevel / 100.0);
667 }
668 
669 static void
670 mix_clicked(void)
671 {
672     STSample* sample = &xm->instruments[curi].samples[curs];
673     STSample* sample1 = &xm->instruments[desti].samples[dests];
674     guint32 length1 = sample->sample.length, length2 = sample1->sample.length;
675 
676     /* Silently ignore empty samples */
677     if ((!sample->sample.data) || (!sample1->sample.data))
678         return;
679     if ((!length1) || (!length2))
680         return;
681 
682     if (curi == desti && curs == dests) {
683         same_sample_warning();
684         return;
685     }
686 
687     if (!sample_editor_check_and_log_sample(sample, N_("Mixing samples"),
688             HISTORY_FLAG_LOG_INS | HISTORY_FLAG_LOG_SMP |
689                 HISTORY_SET_PAGE(NOTEBOOK_PAGE_SAMPLE_EDITOR),
690             MAX(length1, length2)))
691         return;
692 
693     g_mutex_lock(&sample->sample.lock);
694     if (length1 < length2) {
695         sample->sample.data = realloc(sample->sample.data, length2 * sizeof(sample->sample.data[0]));
696         sample->sample.length = length2;
697     }
698     mix_samples(sample, sample, sample1, length1, length2, mixlevel);
699 
700     g_mutex_unlock(&sample->sample.lock);
701     sample_editor_update();
702 }
703 
704 static void
705 mixer_preview(void)
706 {
707     STSample* sample = &xm->instruments[curi].samples[curs];
708     STSample* sample1 = &xm->instruments[desti].samples[dests];
709     guint32 length1 = sample->sample.length, length2 = sample1->sample.length, length;
710     gpointer sampledata;
711 
712     /* Silently ignore empty samples */
713     if ((!sample->sample.data) || (!sample1->sample.data))
714         return;
715     if ((!length1) || (!length2))
716         return;
717 
718     if (curi == desti && curs == dests) {
719         same_sample_warning();
720         return;
721     }
722 
723     length = MAX(length1, length2);
724     /* Indeed, we need to update the sample data only if either a mixlevel or one of the samples
725        is changed (instrument/sample number or the sample data itself. But tracking the sample
726        data modifications (by editing, loading new samples / instrumens / module) is rather
727        complicated. So we update te sample data all the time though... */
728     if (!prev_sample.sample.data) {
729         prev_sample.sample.data = malloc(length * sizeof(sample->sample.data[0]));
730         prev_alloc_length = length;
731     } else if (prev_alloc_length < length) {
732         g_free(prev_sample.sample.data);
733         prev_sample.sample.data = malloc(length * sizeof(sample->sample.data[0]));
734         prev_alloc_length = length;
735     }
736 
737     sampledata = prev_sample.sample.data;
738     memcpy(&prev_sample, sample, sizeof(prev_sample));
739     prev_sample.sample.data = sampledata;
740     mix_samples(&prev_sample, sample, sample1, length1, length2, mixlevel);
741     prev_sample.sample.length = length;
742 
743     gui_play_stop();
744     gui_play_note_full(0, mix_prev_note + 1, &prev_sample, 0, prev_sample.sample.length, FALSE);
745 }
746 
747 void modinfo_page_create(GtkNotebook* nb)
748 {
749     GtkWidget *hbox, *thing, *vbox, *expndr, *hbox2, *vbox2, *hbox3, *alignment, *frame, *prev_note_spin;
750     GtkAdjustment *adj;
751     GtkListStore* list_store;
752     GtkTreeIter iter;
753     GtkTreeModel* model;
754     const gchar* ititles[3] = { "n", N_("Instrument Name"), N_("#smpl") };
755     const gchar* stitles[2] = { "n", N_("Sample Name") };
756     GType itypes[3] = { G_TYPE_INT, G_TYPE_STRING, G_TYPE_INT };
757     GType stypes[2] = { G_TYPE_INT, G_TYPE_STRING };
758     const gfloat ialignments[3] = { 0.5, 0.0, 0.5 };
759     const gfloat salignments[2] = { 0.5, 0.0 };
760     const gboolean iexpands[3] = { FALSE, TRUE, FALSE };
761     const gboolean sexpands[3] = { FALSE, TRUE };
762     const gchar* freqlabels[] = { N_("Linear"), N_("Amiga") };
763     gint i, width, height;
764 
765     vbox = gtk_vbox_new(FALSE, 4);
766     gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
767     gtk_notebook_append_page(nb, vbox, gtk_label_new(_("Module Info")));
768     gtk_widget_show(vbox);
769 
770     hbox = gtk_hbox_new(TRUE, 10);
771     gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
772     gtk_widget_show(hbox);
773 
774     /* Main instruments and samples lists */
775     ilist = gui_list_in_scrolled_window_full(3, ititles, hbox, itypes, ialignments,
776         iexpands, GTK_SELECTION_BROWSE, TRUE, TRUE, &thing,
777         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
778     gtk_widget_show_all(thing);
779     list_store = GUI_GET_LIST_STORE(ilist);
780     model = gui_list_freeze(ilist);
781     for (i = 1; i <= ST_NUM_INSTRUMENTS(xm); i++) {
782         gtk_list_store_append(list_store, &iter);
783         gtk_list_store_set(list_store, &iter, 0, i, 1, "", 2, 0, -1);
784     }
785     gui_list_thaw(ilist, model);
786     gui_list_handle_selection(ilist, G_CALLBACK(ilist_select), NULL);
787 
788     slist = gui_list_in_scrolled_window_full(2, stitles, hbox, stypes, salignments,
789         sexpands, GTK_SELECTION_BROWSE, TRUE, TRUE, &thing,
790         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
791     gtk_widget_show_all(thing);
792     list_store = GUI_GET_LIST_STORE(slist);
793     model = gui_list_freeze(slist);
794     for (i = 0; i < ST_NUM_SAMPLES(&xm->instruments[0]); i++) {
795         gtk_list_store_append(list_store, &iter);
796         gtk_list_store_set(list_store, &iter, 0, i, 1, "", -1);
797     }
798     gui_list_thaw(slist, model);
799     gui_list_handle_selection(slist, G_CALLBACK(slist_select), NULL);
800 
801     /* Secondary instruments and samples lists for extended functions */
802     ilist2 = gui_list_in_scrolled_window_full(3, ititles, hbox, itypes, ialignments,
803         iexpands, GTK_SELECTION_BROWSE, TRUE, TRUE, &sw1,
804         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
805     gtk_widget_show(ilist2);
806     list_store = GUI_GET_LIST_STORE(ilist2);
807     model = gui_list_freeze(ilist2);
808     for (i = 1; i <= ST_NUM_INSTRUMENTS(xm); i++) {
809         gtk_list_store_append(list_store, &iter);
810         gtk_list_store_set(list_store, &iter, 0, i, 1, "", 2, 0, -1);
811     }
812     gui_list_thaw(ilist2, model);
813     gui_list_handle_selection(ilist2, G_CALLBACK(ilist2_select), NULL);
814 
815     slist2 = gui_list_in_scrolled_window_full(2, stitles, hbox, stypes, salignments,
816         sexpands, GTK_SELECTION_BROWSE, TRUE, TRUE, &sw2,
817         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
818     gtk_widget_show(slist2);
819     list_store = GUI_GET_LIST_STORE(slist2);
820     model = gui_list_freeze(slist2);
821     for (i = 0; i < ST_NUM_SAMPLES(&xm->instruments[0]); i++) {
822         gtk_list_store_append(list_store, &iter);
823         gtk_list_store_set(list_store, &iter, 0, i, 1, "", -1);
824     }
825     gui_list_thaw(slist2, model);
826     gui_list_handle_selection(slist2, G_CALLBACK(slist2_select), NULL);
827 
828     /* Module metadata */
829     hbox = gtk_hbox_new(FALSE, 4);
830     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
831 
832     thing = gtk_label_new(_("Songname:"));
833     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, TRUE, 0);
834 
835     gui_get_text_entry(20, songname_changed, &songname);
836     gtk_box_pack_start(GTK_BOX(hbox), songname, TRUE, TRUE, 0);
837 
838     add_empty_hbox(hbox);
839     thing = make_labelled_radio_group_box(_("Frequencies:"), freqlabels, freqmode_w, freqmode_changed);
840     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, TRUE, 0);
841     add_empty_hbox(hbox);
842 
843     ptmode_toggle = gtk_check_button_new_with_label(_("ProTracker Mode"));
844     gtk_box_pack_start(GTK_BOX(hbox), ptmode_toggle, FALSE, TRUE, 0);
845     gtk_widget_show(ptmode_toggle);
846     g_signal_connect(ptmode_toggle, "toggled",
847         G_CALLBACK(ptmode_changed), NULL);
848 
849     add_empty_hbox(hbox);
850     gtk_widget_show_all(hbox);
851 
852     thing = gtk_hseparator_new();
853     gtk_box_pack_start(GTK_BOX(vbox), thing, FALSE, FALSE, 0);
854     gtk_widget_show(thing);
855 
856     /* Extended editor */
857     expndr = gtk_expander_new(_("Extended Instrument/Sample Editor"));
858     gtk_expander_set_expanded(GTK_EXPANDER(expndr), FALSE);
859     gtk_box_pack_end(GTK_BOX(vbox), expndr, FALSE, FALSE, 0);
860     g_signal_connect(expndr, "notify::expanded", G_CALLBACK(exp_change), NULL);
861     hbox = gtk_hbox_new(FALSE, 4);
862     gtk_container_add(GTK_CONTAINER(expndr), hbox);
863 
864     vbox = gtk_vbox_new(TRUE, 2);
865     gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
866     ins1_label = add_label(N_("Ins. 1"), "1", vbox);
867     gui_get_pixel_size(ins1_label, "000", &width, &height);
868     gtk_widget_set_size_request(ins1_label, width, -1);
869     smp1_label = add_label(N_("Smp. 1"), "0", vbox);
870     gtk_widget_set_size_request(smp1_label, width, -1);
871     ins2_label = add_label(N_("Ins. 2"), "1", vbox);
872     gtk_widget_set_size_request(ins2_label, width, -1);
873     smp2_label = add_label(N_("Smp. 2"), "0", vbox);
874     gtk_widget_set_size_request(smp2_label, width, -1);
875 
876     thing = gtk_vseparator_new();
877     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
878 
879     vbox = gtk_vbox_new(TRUE, 2);
880     gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
881 
882     thing = gtk_button_new_with_label(_("I1 => I2"));
883     gtk_widget_set_tooltip_text(thing, _("Copy Instrument 1 to Instrument 2"));
884     gtk_box_pack_start(GTK_BOX(vbox), thing, FALSE, FALSE, 0);
885     g_signal_connect_swapped(thing, "clicked", G_CALLBACK(modinfo_xcopy_ins), GINT_TO_POINTER(0));
886 
887     thing = gtk_button_new_with_label(_("I1 <=> I2"));
888     gtk_widget_set_tooltip_text(thing, _("Exchange Instruments 1 and 2"));
889     gtk_box_pack_start(GTK_BOX(vbox), thing, FALSE, FALSE, 0);
890     g_signal_connect_swapped(thing, "clicked", G_CALLBACK(modinfo_xcopy_ins), GINT_TO_POINTER(1));
891 
892     thing = gtk_button_new_with_label(_("S1 => S2"));
893     gtk_widget_set_tooltip_text(thing, _("Copy Sample 1 to Sample 2"));
894     gtk_box_pack_start(GTK_BOX(vbox), thing, FALSE, FALSE, 0);
895     g_signal_connect_swapped(thing, "clicked", G_CALLBACK(modinfo_xcopy_smp), GINT_TO_POINTER(0));
896 
897     thing = gtk_button_new_with_label(_("S1 <=> S2"));
898     gtk_widget_set_tooltip_text(thing, _("Exchange Samples 1 and 2"));
899     gtk_box_pack_start(GTK_BOX(vbox), thing, FALSE, FALSE, 0);
900     g_signal_connect_swapped(thing, "clicked", G_CALLBACK(modinfo_xcopy_smp), GINT_TO_POINTER(1));
901 
902     thing = gtk_vseparator_new();
903     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
904 
905     /* Mixer */
906 
907     vbox = gtk_vbox_new(FALSE, 2);
908     gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
909     thing = gtk_label_new(_("Mixing balance"));
910     gtk_misc_set_alignment(GTK_MISC(thing), 0.5, 0.5);
911     gtk_box_pack_start(GTK_BOX(vbox), thing, FALSE, TRUE, 0);
912 
913     hbox2 = gtk_hbox_new(FALSE, 2);
914     gtk_box_pack_start(GTK_BOX(vbox), hbox2, FALSE, TRUE, 0);
915     vbox2 = gtk_vbox_new(FALSE, 2);
916     gtk_box_pack_start(GTK_BOX(hbox2), vbox2, TRUE, TRUE, 0);
917     hbox3 = gtk_hbox_new(FALSE, 0);
918     gtk_box_pack_start(GTK_BOX(vbox2), hbox3, FALSE, TRUE, 0);
919 
920     mix_label1 = gtk_label_new(_("I1, S0"));
921     gtk_box_pack_start(GTK_BOX(hbox3), mix_label1, FALSE, TRUE, 0);
922 
923     mix_label2 = gtk_label_new(_("I1, S0"));
924     gtk_box_pack_end(GTK_BOX(hbox3), mix_label2, FALSE, TRUE, 0);
925 
926     adj = GTK_ADJUSTMENT(gtk_adjustment_new(50.0, 0.0, 100.0, 1.0, 10.0, 0.0));
927     thing = gtk_hscale_new(adj);
928     gtk_scale_set_draw_value(GTK_SCALE(thing), FALSE);
929     gtk_box_pack_start(GTK_BOX(vbox2), thing, FALSE, TRUE, 0);
930     g_signal_connect(adj, "value-changed", G_CALLBACK(mixlevel_changed), NULL);
931 
932     alignment = gtk_alignment_new(0.5, 1.0, 0.0, 0.0);
933     gtk_box_pack_start(GTK_BOX(hbox2), alignment, FALSE, FALSE, 0);
934     hbox3 = gtk_hbox_new(FALSE, 2);
935     gtk_container_add(GTK_CONTAINER(alignment), hbox3);
936 
937     thing = extspinbutton_new(adj, 0.0, 0, TRUE);
938     gtk_box_pack_start(GTK_BOX(hbox3), thing, FALSE, FALSE, 0);
939     thing = gtk_label_new("%");
940     gtk_box_pack_start(GTK_BOX(hbox3), thing, FALSE, FALSE, 0);
941 
942     hbox2 = gtk_hbox_new(FALSE, 4);
943     gtk_box_pack_end(GTK_BOX(vbox), hbox2, FALSE, TRUE, 0);
944 
945     thing = gtk_label_new(_("Note"));
946     gtk_widget_set_tooltip_text(thing, _("Note to demonstrate result"));
947     gtk_box_pack_start(GTK_BOX(hbox2), thing, FALSE, FALSE, 0);
948 
949     prev_note_spin = extspinbutton_new(
950         GTK_ADJUSTMENT(gtk_adjustment_new(48.0, 0.0, NUM_NOTES - 1, 1.0, 5.0, 0.0)),
951         0.0, 0, TRUE);
952     gtk_box_pack_start(GTK_BOX(hbox2), prev_note_spin, FALSE, FALSE, 0);
953 
954     frame = gtk_frame_new(NULL);
955     gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
956     gtk_box_pack_start(GTK_BOX(hbox2), frame, FALSE, FALSE, 0);
957     thing = gtk_label_new("");
958     gui_get_pixel_size(thing, " C#4 ", &width, &height);
959     gtk_widget_set_size_request(thing, width, -1);
960     gtk_container_add(GTK_CONTAINER(frame), thing);
961     g_signal_connect(prev_note_spin, "value-changed", G_CALLBACK(mix_prev_note_changed), thing);
962     mix_prev_note_changed(GTK_SPIN_BUTTON(prev_note_spin), GTK_LABEL(thing));
963 
964     thing = gtk_button_new_with_label(_("Demo"));
965     gtk_widget_set_tooltip_text(thing, _("Play the mixed sample without module modification"));
966     gtk_box_pack_start(GTK_BOX(hbox2), thing, TRUE, TRUE, 0);
967     g_signal_connect(thing, "clicked", G_CALLBACK(mixer_preview), NULL);
968 
969     thing = gtk_button_new_with_label(_("Mix!"));
970     gtk_widget_set_tooltip_text(thing,
971         _("Mix sample 1 of instrument 1 with sample 2 of instrument 2 with the given balance ratio"));
972     gtk_box_pack_start(GTK_BOX(hbox2), thing, TRUE, TRUE, 0);
973     g_signal_connect(thing, "clicked", G_CALLBACK(mix_clicked), NULL);
974 
975     thing = gtk_vseparator_new();
976     gtk_box_pack_start(GTK_BOX(hbox), thing, FALSE, FALSE, 0);
977 
978     /* Tuning facility, tuning sample */
979     tuning = tuner_new(FALSE, _("Tuning: "));
980     gtk_box_pack_start(GTK_BOX(hbox), tuning->frame, FALSE, FALSE, 0);
981     update_labels(tuning, ins1_label, mix_label1, 1, 0, 0);
982 
983     hbox2 = gtk_hbox_new(FALSE, 4);
984     gtk_container_set_border_width(GTK_CONTAINER(hbox2), 4);
985     gtk_container_add(GTK_CONTAINER(tuning->frame), hbox2);
986     vbox = gtk_vbox_new(TRUE, 2);
987     gtk_box_pack_start(GTK_BOX(hbox2), vbox, FALSE, FALSE, 0);
988 
989     thing = gtk_hscale_new(sample_editor_get_adjustment(SAMPLE_EDITOR_FINETUNE));
990     gtk_scale_set_draw_value(GTK_SCALE(thing), FALSE);
991     gtk_box_pack_start(GTK_BOX(vbox), thing, FALSE, FALSE, 0);
992     put_labelled_spin_button(_("Finetune"), sample_editor_get_adjustment(SAMPLE_EDITOR_FINETUNE), vbox);
993     put_labelled_spin_button(_("RelNote"), sample_editor_get_adjustment(SAMPLE_EDITOR_RELNOTE), vbox);
994 
995     vbox = tuner_populate(tuning);
996     gtk_box_pack_start(GTK_BOX(hbox2), vbox, FALSE, FALSE, 0);
997 
998     /* Reference sample */
999     ref = tuner_new(TRUE, _("Reference: "));
1000     gtk_box_pack_start(GTK_BOX(hbox), ref->frame, FALSE, FALSE, 0);
1001 
1002     vbox = tuner_populate(ref);
1003     gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
1004     gtk_container_add(GTK_CONTAINER(ref->frame), vbox);
1005     update_labels(ref, ins2_label, mix_label2, 1, 0, 0);
1006 
1007     gtk_widget_show_all(expndr);
1008 }
1009 
1010 gboolean
1011 modinfo_page_handle_keys(int shift,
1012     int ctrl,
1013     int alt,
1014     guint32 keyval,
1015     gboolean pressed)
1016 {
1017     gint modifiers = ENCODE_MODIFIERS(shift, ctrl, alt);
1018     gint i = keys_get_key_meaning(keyval, modifiers, -1);
1019     gboolean handled = FALSE, samples_focused;
1020 
1021     if (i != -1 && KEYS_MEANING_TYPE(i) == KEYS_MEANING_NOTE) {
1022         if (tuning->mode == MODE_KEYBOARD && is_tuning) {
1023             STSample* sample = &xm->instruments[curi].samples[curs];
1024 
1025             handled = gui_play_note_no_repeat(keyval, modifiers, pressed,
1026                 sample, 0, sample->sample.length, 1, FALSE, NULL, TRUE);
1027 
1028             /* handled means that it's a real, not fake, keypress and the note is correct */
1029             if (ref->mode == MODE_COUPLED && handled) {
1030                 if (pressed) {
1031                     sample = &xm->instruments[desti].samples[dests];
1032                     if (sample)
1033                         gui_play_note_full(0, ref->note + 1, sample, 0, sample->sample.length, FALSE);
1034                 } else
1035                     gui_stop_note(0);
1036             }
1037         } else
1038             track_editor_do_the_note_key(i, pressed, keyval, ENCODE_MODIFIERS(shift, ctrl, alt), TRUE);
1039         return TRUE;
1040     }
1041 
1042     if (!pressed)
1043         return FALSE;
1044 
1045     samples_focused = (GTK_WINDOW(mainwindow)->focus_widget == slist);
1046     switch (keyval) {
1047     case GDK_Tab:
1048     case GDK_ISO_Left_Tab:
1049         gtk_window_set_focus(GTK_WINDOW(mainwindow), samples_focused ? ilist : slist);
1050         handled = TRUE;
1051         break;
1052     case GDK_Up:
1053         if (samples_focused)
1054             gui_offset_current_sample(shift ? -4 : -1);
1055         else
1056             gui_offset_current_instrument(shift ? -5 : -1);
1057         handled = TRUE;
1058         break;
1059     case GDK_Down:
1060         if (samples_focused)
1061             gui_offset_current_sample(shift ? 4 : 1);
1062         else
1063             gui_offset_current_instrument(shift ? 5 : 1);
1064         handled = TRUE;
1065         break;
1066     case GDK_Delete:
1067         handled = instrument_editor_clear_current_instrument();
1068         break;
1069     default:
1070         break;
1071     }
1072     return handled;
1073 }
1074 
1075 void modinfo_stop_cycling(void)
1076 {
1077     if (tuning->timer != -1) {
1078         g_source_remove(tuning->timer);
1079         tuning->timer = -1;
1080     }
1081     if (ref->timer != -1) {
1082         g_source_remove(ref->timer);
1083         ref->timer = -1;
1084     }
1085 }
1086 
1087 void
1088 modinfo_update_instrument(int n)
1089 {
1090     modinfo_update_instrument_full(n, FALSE);
1091     modinfo_update_instrument_full(n, TRUE);
1092 }
1093 
1094 void modinfo_update_sample(int n)
1095 {
1096     modinfo_update_sample_full(curi, n, FALSE);
1097     modinfo_update_sample_full(curi, n, TRUE);
1098 }
1099 
1100 void modinfo_update_all(void)
1101 {
1102     gint i;
1103     const gint freq_index = (xm->flags & XM_FLAGS_AMIGA_FREQ) != 0 ? 1 : 0;
1104 
1105     for (i = 0; i < ST_NUM_INSTRUMENTS(xm); i++)
1106         modinfo_update_instrument(i);
1107 
1108     gtk_entry_set_text(GTK_ENTRY(songname), xm->utf_name);
1109 
1110     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ptmode_toggle), xm->flags & XM_FLAGS_IS_MOD);
1111     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(freqmode_w[freq_index]), TRUE);
1112 }
1113 
1114 void modinfo_set_current_instrument(int n)
1115 {
1116     g_return_if_fail(n >= 0 && n < ST_NUM_INSTRUMENTS(xm));
1117 
1118     gui_list_select(ilist, n, FALSE, 0.0);
1119     modinfo_update_all_samples(n, FALSE);
1120 }
1121 
1122 void modinfo_set_current_sample(int n)
1123 {
1124     g_return_if_fail(n >= 0 && n < ST_NUM_SAMPLES(&xm->instruments[0]));
1125 
1126     gui_list_select(slist, n, FALSE, 0.0);
1127 }
1128 
1129 gint modinfo_get_current_sample(void)
1130 {
1131     return curs;
1132 }
1133