/*****************************************************************************
* Gnome Wave Cleaner Version 0.19
* Copyright (C) 2001 Jeffrey J. Welty
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*******************************************************************************/
/* markers.c */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <libgen.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <gnome.h>
#include "gtkledbar.h"
#include "gwc.h"
long cdtext_length;
char *cdtext_data = NULL;
/* The file selection widget and the string to store the chosen filename */
GtkWidget *file_selector;
gchar *selected_filename;
gchar save_cdrdao_toc_filename[255] ;
extern long num_song_markers, song_markers[] ;
extern gchar wave_filename[] ;
extern struct sound_prefs prefs ;
extern struct view audio_view;
extern double song_key_highlight_interval ;
extern double song_mark_silence ;
char *find_text(long length,char *data, char *str) {
char *ret = NULL;
char *end = data + length;
int len;
while (data < end && ret == NULL) {
len = strlen(data);
if (strcmp(data, str) == 0) {
ret = data + len + 1;
} else {
data = data + len + 1;
/* Skip field value */
len = strlen(data);
data = data + len + 1;
}
}
return ret;
}
int only_blank(char *str)
{
while (*str != 0) {
if (*str++ != ' ')
return 0;
}
return 1;
}
static int use_song_marker_pairs ; /* song markers are positioned at start AND end of each song? */
#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
/* CD Tracks must be multiple of 588 or they will be padded with zeros */
#define SONG_BLOCK_LEN 588
void cdrdao_toc_info(char *filename)
{
GtkWidget *dlg ;
GtkWidget *dialog_table ;
GtkWidget *song_table ;
int dres ;
int row = 0;
struct {
char *title;
char *fieldid;
char *init;
int always_write;
GtkWidget *widget;
} album_info[] =
{
{"Language", "LANGUAGE", "EN", 1, NULL}, /* This is special and must be first */
{"Title", "TITLE", "", 1, NULL},
{"Performer", "PERFORMER", "", 0, NULL},
{"Songwriter", "SONGWRITER", "", 0, NULL},
{"Arranger", "ARRANGER", "", 0, NULL},
{"Composer", "COMPOSER", "", 0, NULL},
{"Disc_ID", "DISC_ID", "", 0, NULL},
{"Message", "MESSAGE", "", 1, NULL}
};
struct {
char *title;
char *fieldid;
char *init;
int always_write;
} song_info[] =
{
{"Title", "TITLE", "", 1},
{"Message", "MESSAGE", "", 1}
};
GtkWidget *song_widget[MAX_MARKERS+1][ARRAYSIZE(song_info)];
int i,j;
GtkWidget *scrolled;
int new_cdtext_length = 0;
char *new_cdtext;
int new_cdtext_loc;
char buf[200], buf2[200];
dlg = gtk_dialog_new_with_buttons("Cdrdao CD Text Information",
NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_OK, GTK_RESPONSE_OK,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL, NULL);
gtk_window_set_policy(GTK_WINDOW(dlg), FALSE, TRUE, FALSE);
dialog_table = gtk_table_new(5,2,0) ;
gtk_table_set_row_spacings(GTK_TABLE(dialog_table), 4) ;
gtk_table_set_col_spacings(GTK_TABLE(dialog_table), 6) ;
gtk_widget_show (dialog_table);
for (i = 0; i < ARRAYSIZE(album_info); i++) {
char *init;
init = find_text(cdtext_length, cdtext_data, album_info[i].fieldid);
if (init == NULL) {
init = album_info[i].init;
}
album_info[i].widget = add_number_entry_with_label(init, album_info[i].title, dialog_table, row++) ;
}
song_table = gtk_table_new(5,2,0) ;
gtk_table_set_row_spacings(GTK_TABLE(song_table), 4) ;
gtk_table_set_col_spacings(GTK_TABLE(song_table), 6) ;
scrolled = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), song_table);
gtk_widget_set_usize(scrolled,300,300);
gtk_widget_show (scrolled);
gtk_widget_show (song_table);
row = 0;
for (j = 0; j < num_song_markers+1; j += 1+use_song_marker_pairs) {
for (i = 0; i < ARRAYSIZE(song_info); i++) {
char *init;
snprintf(buf, sizeof(buf), "Song %2d %s", j+1, song_info[i].title);
/* Duplicated below, and new_cdtext_length adjusted by 3 for %3d */
snprintf(buf2, sizeof(buf2), "%3d%s", j+1, song_info[i].fieldid);
init = find_text(cdtext_length, cdtext_data, buf2);
if (init == NULL) {
init = song_info[i].init;
}
song_widget[j][i] = add_number_entry_with_label(init, buf, song_table, row++) ;
}
}
gtk_box_pack_start (GTK_BOX (GTK_DIALOG(dlg)->vbox), dialog_table, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG(dlg)->vbox), scrolled, TRUE, TRUE, 0);
dres = gwc_dialog_run(GTK_DIALOG(dlg)) ;
if(dres == 0) {
FILE *toc;
toc = fopen(filename,"w");
if (toc == NULL) {
snprintf(buf, sizeof(buf), "Unable to open %s: %s", filename, strerror(errno));
warning(buf);
} else {
int found_text = 0;
for (i = 0; i < ARRAYSIZE(album_info); i++) {
char *str;
str = (char *)gtk_entry_get_text(GTK_ENTRY(album_info[i].widget));
/* +2 for null at end of both strings*/
new_cdtext_length += strlen(str) + strlen(album_info[i].fieldid) + 2 ;
if (strlen(album_info[i].init) == 0 && !only_blank(str))
found_text = 1;;
}
for (j = 0; j < num_song_markers+(1-use_song_marker_pairs); j += 1+use_song_marker_pairs) {
for (i = 0; i < ARRAYSIZE(song_info); i++) {
char *str;
str = (char *)gtk_entry_get_text(GTK_ENTRY(song_widget[j][i]));
/* We add 3 digit song # to fieldid when storing */
new_cdtext_length += strlen(str) + strlen(song_info[i].fieldid) + 3 + 2;
if (!only_blank(str))
found_text = 1;;
}
}
new_cdtext_loc = 0;
if (found_text) {
new_cdtext = calloc(new_cdtext_length, 1);
fprintf(toc, "CD_TEXT {\n LANGUAGE_MAP {\n 0: %s\n }\n", gtk_entry_get_text(GTK_ENTRY(album_info[0].widget)));
fprintf(toc, " LANGUAGE 0 {\n");
for (i = 0; i < ARRAYSIZE(album_info); i++) {
char *str;
str = (char *)gtk_entry_get_text(GTK_ENTRY(album_info[i].widget));
if (album_info[i].always_write || !only_blank(str)) {
/* First entry, language added to file above */
if (i > 0) {
fprintf(toc, " %s \"%s\"\n", album_info[i].fieldid, str);
}
strcat(&new_cdtext[new_cdtext_loc], album_info[i].fieldid);
new_cdtext_loc += strlen(&new_cdtext[new_cdtext_loc]) + 1;
strcat(&new_cdtext[new_cdtext_loc], str);
new_cdtext_loc += strlen(&new_cdtext[new_cdtext_loc]) + 1;
}
}
fprintf(toc, " }\n}\n");
} else {
new_cdtext = NULL;
}
for (j = 0; j < num_song_markers+(1-use_song_marker_pairs); j += 1+use_song_marker_pairs) {
long end;
int j1 = j+1 ;
long length ;
long start ;
if(use_song_marker_pairs) {
start = song_markers[j] ;
if(j1 < num_song_markers) {
length = song_markers[j+1] - start + 1 ;
} else {
/* no last song marker in last pair, assume end of audio for end of last track */
length = (prefs.n_samples-1) - start + 1 ;
}
} else {
if (j == 0) {
start = 0 ;
length = song_markers[j] - start + 1 ;
} else if (j == num_song_markers) {
start = song_markers[j-1] ;
length = (prefs.n_samples-1) - start + 1 ;
} else {
start = song_markers[j-1] ;
length = song_markers[j] - start + 1 ;
}
}
#define AUDIO_BLOCK_LEN 588
length = ((length + AUDIO_BLOCK_LEN / 2) / AUDIO_BLOCK_LEN) * AUDIO_BLOCK_LEN ;
end = start + length -1 ;
while(end > (prefs.n_samples-1) && end > 2*AUDIO_BLOCK_LEN) {
end -= AUDIO_BLOCK_LEN ;
}
length = end - start + 1 ;
fprintf(toc, "TRACK AUDIO\n");
if (found_text) {
fprintf(toc, " CD_TEXT {\n LANGUAGE 0 {\n");
for (i = 0; i < ARRAYSIZE(song_info); i++) {
char *str;
str = (char *)gtk_entry_get_text(GTK_ENTRY(song_widget[j][i]));
if (song_info[i].always_write || !only_blank(str)) {
fprintf(toc, " %s \"%s\"\n", song_info[i].fieldid, str);
/* Duplicated above */
snprintf(buf, sizeof(buf), "%3d%s", j+1, song_info[i].fieldid);
strcat(&new_cdtext[new_cdtext_loc], buf);
new_cdtext_loc += strlen(&new_cdtext[new_cdtext_loc]) + 1;
strcat(&new_cdtext[new_cdtext_loc], str);
new_cdtext_loc += strlen(&new_cdtext[new_cdtext_loc]) + 1;
}
}
fprintf(toc, " }\n }\n");
}
fprintf(toc, " FILE \"%s\" %ld %ld\n", wave_filename, start, end - start) ;
}
fclose(toc);
if (cdtext_data != NULL) {
free(cdtext_data);
}
cdtext_data = new_cdtext;
/* Loc is the actual length we filled */
cdtext_length = new_cdtext_loc;
}
}
gtk_widget_destroy(dlg) ;
}
void store_cdrdao_toc(GtkFileSelection * selector, gpointer user_data)
{
int fd_new;
gtk_widget_hide_all (GTK_WIDGET(file_selector));
strcpy(save_cdrdao_toc_filename,
gtk_file_selection_get_filename(GTK_FILE_SELECTION(file_selector))) ;
if(strcmp(save_cdrdao_toc_filename, wave_filename)) {
int l ;
l = strlen(save_cdrdao_toc_filename) ;
d_print("Save cdrdao_toc to %s\n", save_cdrdao_toc_filename) ;
fd_new = open(save_cdrdao_toc_filename, O_RDONLY) ;
if(fd_new > -1) {
char buf[1000] ;
close(fd_new) ;
sprintf(buf, "%s exists, overwrite ?", save_cdrdao_toc_filename) ;
if(yesno(buf)) {
return ;
}
}
cdrdao_toc_info(save_cdrdao_toc_filename) ;
} else {
warning("Cannot save selection over the currently open file!") ;
}
}
void save_cdrdao_toc(GtkWidget * widget, gpointer data)
{
char pathname[256] = "./cdrdao.toc";
if (num_song_markers == 0) {
info("No songs marked, Use Markers->Mark Songs");
} else {
/* Create the selector */
file_selector =
gtk_file_selection_new("Filename to save cdrdao toc to:");
gtk_file_selection_set_filename(GTK_FILE_SELECTION(file_selector), pathname) ;
gtk_signal_connect(GTK_OBJECT
(GTK_FILE_SELECTION(file_selector)->ok_button),
"clicked", GTK_SIGNAL_FUNC(store_cdrdao_toc), NULL);
/* Ensure that the dialog box is destroyed when the user clicks a button. */
gtk_signal_connect_object(GTK_OBJECT
(GTK_FILE_SELECTION(file_selector)->
ok_button), "clicked",
GTK_SIGNAL_FUNC(gtk_widget_destroy),
(gpointer) file_selector);
gtk_signal_connect_object(GTK_OBJECT
(GTK_FILE_SELECTION(file_selector)->
cancel_button), "clicked",
GTK_SIGNAL_FUNC(gtk_widget_destroy),
(gpointer) file_selector);
/* Display the dialog */
gtk_widget_show(file_selector);
}
}
void save_cdrdao_tocs(GtkWidget * widget, gpointer data)
{
use_song_marker_pairs = 0 ;
save_cdrdao_toc(widget, data) ;
}
void save_cdrdao_tocp(GtkWidget * widget, gpointer data)
{
use_song_marker_pairs = 1 ;
save_cdrdao_toc(widget, data) ;
}
int _add_song_marker(long loc)
{
int i,j;
if (num_song_markers >= MAX_MARKERS - 1) {
set_status_text("No more song markers available");
return 0 ;
} else {
for (i = 0; i < num_song_markers; i++) {
if (song_markers[i] > loc) {
break;
}
}
for (j = num_song_markers - 1; j >= i; j--) {
song_markers[j+1] = song_markers[j];
}
song_markers[i] = loc ;
num_song_markers++;
return 1 ;
}
}
void add_song_marker(void)
{
long loc = audio_view.selected_first_sample;
if(_add_song_marker(loc))
main_redraw(FALSE, TRUE);
}
void add_song_marker_pair(void)
{
int r ;
long loc = audio_view.selected_first_sample;
r = _add_song_marker(loc) ;
loc = audio_view.selected_last_sample;
r += _add_song_marker(loc) ;
if(r > 0)
main_redraw(FALSE, TRUE);
}
void delete_song_marker(void)
{
int i,j;
i = 0;
while (i < num_song_markers) {
if (song_markers[i] >= audio_view.selected_first_sample &&
song_markers[i] <= audio_view.selected_last_sample) {
for (j = i; j < num_song_markers - 1; j++) {
song_markers[j] = song_markers[j+1];
}
num_song_markers--;
} else {
i++;
}
}
main_redraw(FALSE, TRUE);
}
void adjust_song_marker_positions(long pos, long delta)
{
int i,j;
i = 0;
while (i < num_song_markers) {
if (song_markers[i] >= pos) {
song_markers[i] += delta;
if (song_markers[i] <= pos || song_markers[i] >= prefs.n_samples) {
for (j = i; j < num_song_markers - 1; j++) {
song_markers[j] = song_markers[j+1];
}
num_song_markers--;
} else {
i++;
}
} else {
i++;
}
}
}
void move_song_marker(void)
{
int i;
long loc = audio_view.selected_first_sample;
long err;
int min_err_loc = 0;
long min_err = LONG_MAX;
if (num_song_markers == 0) {
set_status_text("No song markers");
} else {
for (i = 0; i < num_song_markers; i++) {
err = abs(loc - song_markers[i]);
if (err < min_err) {
min_err = err;
min_err_loc = i;
}
}
song_markers[min_err_loc] = (loc / SONG_BLOCK_LEN) * SONG_BLOCK_LEN;
main_redraw(FALSE, TRUE);
}
}
void select_song_marker(void)
{
int i;
long loc = audio_view.selected_last_sample;
if (num_song_markers == 0) {
set_status_text("No song markers");
} else {
if (loc > song_markers[num_song_markers-1]) {
loc = 0;
}
for (i = 0; i < num_song_markers && song_markers[i] < loc; i++);
audio_view.selected_last_sample = MIN(prefs.n_samples - 1, song_markers[i] + prefs.rate * song_key_highlight_interval / 2) ;
audio_view.selected_first_sample = MAX(0, song_markers[i] - prefs.rate * song_key_highlight_interval / 2) ;
audio_view.selection_region = TRUE ;
main_redraw(FALSE, TRUE);
}
}
#define DETECT_GAPS_NOT
#ifdef DETECT_GAPS
double sample_rms(struct sample_block *sample_buffer, int i, int w)
{
int istart=i-w/2 ;
if(istart < 0) istart=0 ;
int iend=i+w/2 ;
double sum=0 ;
double n=iend-istart+1.0 ;
for(i=istart ; i <= iend ; i++) {
sum += (sample_buffer[i].rms[0]+sample_buffer[i].rms[1])/2.0 ;
}
return sum/n ;
}
#endif
void mark_songs(GtkWidget * widget, gpointer data)
{
struct sample_block *sample_buffer ;
int n_blocks ;
int i;
double max_song = 0.0, min_song = 999999999.0;
double song_amp;
double song_window_amp = 0.0;
double *delay;
/* These might be good as preferences */
double MIN_SONG_LEN = 35.0;
long min_song_blocks;
/* Length of sliding window in seconds to average audio over */
double AVG_LEN = song_mark_silence*.75;
int avg_blocks;
long min_silence_blocks;
double SILENCE_EST = .3;
double silence;
double sec_per_block;
double silence_scale;
int found_short;
int valid_delay;
int delay_cntr;
int last_silence;
int silence_cntr;
char buf[200];
int last_song_block;
num_song_markers = 0;
n_blocks = get_sample_buffer(&sample_buffer) ;
if (n_blocks > 0) {
sec_per_block = (double) sample_buffer[0].n_samples / prefs.rate;
} else {
sec_per_block = 0.0;
}
if (n_blocks * sec_per_block < MIN_SONG_LEN * 3) {
snprintf(buf, sizeof(buf), "Must have at least %4.0f seconds of music", MIN_SONG_LEN*3);
info(buf);
return;
}
min_silence_blocks = MAX(.25,song_mark_silence) / sec_per_block;
min_song_blocks = MIN_SONG_LEN / sec_per_block;
avg_blocks = MAX(.25*.75,AVG_LEN) / sec_per_block;
delay = malloc(avg_blocks * sizeof(delay[0]));
/* First find minimum and maximum level in a sliding window */
valid_delay = 0;
delay_cntr = 0;
song_window_amp = 0.0;
for (i = 0; i < avg_blocks; i++)
delay[i] = 0.0;
for (i = min_song_blocks; i < n_blocks - min_song_blocks; i++) {
song_amp = (sample_buffer[i].max_value[0] + sample_buffer[i].max_value[1]);
song_window_amp += song_amp - delay[delay_cntr];
delay[delay_cntr] = song_amp;
delay_cntr = (delay_cntr + 1) % avg_blocks;
if (delay_cntr == 0)
valid_delay = 1;
if (valid_delay) {
if (song_window_amp > max_song)
max_song = song_window_amp;
if (song_window_amp < min_song)
min_song = song_window_amp;
}
}
/* Now step the threshold up until we find something too short to be a */
/* song. */
found_short = 0;
for (silence_scale = 2.0; silence_scale < 32.0 && !found_short; ) {
last_silence = -min_song_blocks;
valid_delay = 0;
delay_cntr = 0;
song_window_amp = 0.0;
silence_cntr = 0;
for (i = 0; i < avg_blocks; i++)
delay[i] = 0.0;
for (i = min_song_blocks; i < n_blocks - min_song_blocks && !found_short; i++) {
song_amp = (sample_buffer[i].max_value[0] + sample_buffer[i].max_value[1]);
song_window_amp += song_amp - delay[delay_cntr];
delay[delay_cntr] = song_amp;
delay_cntr = (delay_cntr + 1) % avg_blocks;
if (delay_cntr == 0)
valid_delay = 1;
if (valid_delay) {
if (song_window_amp > min_song * silence_scale) {
silence_cntr = 0;
} else {
silence_cntr++;
if (silence_cntr > min_silence_blocks) {
if (i - last_silence > min_silence_blocks * 3 && i - last_silence < min_song_blocks) {
found_short = 1;
}
last_silence = i;
}
}
}
}
if (!found_short)
silence_scale *= 1.5;
}
/* Pick a threshold between the minimum level and the two high level from */
/* above. Use it to mark the songs. Might be good to look for minimum */
/* silence level to help center the song break better */
silence = min_song + min_song * silence_scale * SILENCE_EST;
valid_delay = 0;
delay_cntr = 0;
song_window_amp = 0.0;
silence_cntr = 0;
last_silence = 0;
last_song_block = 0;
for (i = 0; i < avg_blocks; i++)
delay[i] = 0.0;
for (i = min_song_blocks; i < n_blocks - min_song_blocks; i++) {
song_amp = (sample_buffer[i].max_value[0] + sample_buffer[i].max_value[1]);
song_window_amp += song_amp - delay[delay_cntr];
delay[delay_cntr] = song_amp;
delay_cntr = (delay_cntr + 1) % avg_blocks;
if (delay_cntr == 0)
valid_delay = 1;
if (valid_delay) {
if (song_window_amp > silence) {
if (last_silence && i - last_song_block > min_song_blocks) {
int loc = (i + (last_silence - min_silence_blocks)) / 2 * sample_buffer[i].n_samples;
song_markers[num_song_markers++] = (loc / SONG_BLOCK_LEN) * SONG_BLOCK_LEN;
if (num_song_markers >= MAX_MARKERS - 2)
break;
last_song_block = i;
}
last_silence = 0;
silence_cntr = 0;
} else {
silence_cntr++;
if (silence_cntr > min_silence_blocks) {
if (!last_silence) {
last_silence = i;
}
}
}
}
}
#ifdef DETECT_GAPS
if(1) {
/* this attempts to detect entire gaps between songs, it does not work
* well yet and should be disabled except for testing... */
long tmp_markers[MAX_MARKERS] ;
int n_tmp = num_song_markers ;
for(i = 0 ; i < num_song_markers ; i++)
tmp_markers[i] = song_markers[i] ;
num_song_markers=0 ;
for(i=0 ; i < n_tmp ; i++) {
int iblk = tmp_markers[i]/SBW ;
#define bsw 256
#define hbsw 64
double midpt_rms = sample_rms(sample_buffer,iblk,bsw) ;
int j ;
for(j = hbsw ; j < min_song_blocks ; j += hbsw) {
double lead_rms = sample_rms(sample_buffer,iblk-j,bsw) ;
if(lead_rms > midpt_rms*1.3)
break ;
}
if(j > hbsw)
song_markers[num_song_markers++] = (iblk-j)*SBW ;
for(j = hbsw ; j < min_song_blocks ; j += hbsw) {
double tail_rms = sample_rms(sample_buffer,iblk+j,bsw) ;
if(tail_rms > midpt_rms*1.5)
break ;
}
if(j > hbsw)
song_markers[num_song_markers++] = (iblk+j)*SBW ;
}
}
#endif
snprintf(buf, sizeof(buf), "Marked %ld songs", num_song_markers + 1);
set_status_text(buf);
free(delay);
main_redraw(FALSE, TRUE) ;
}
|