/* * Copyright (C) 2004-2005 Vadim Berezniker * http://www.kryptolus.com * * 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, 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 GNU Make; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include "stdafx.h" #include "common.h" #include "sabbu.h" #include "sound.h" #include "gui_status_bar.h" #include "sound_out.h" #include "gui_peakfile.h" #include "kry_peakfile.h" gboolean mute[2]; extern struct sabbu app; GMutex *sound_data_mutex = NULL; GMutex *progress_mutex = NULL; GCond *progress_change = NULL; GMutex *progress_data_mutex = NULL; long progress; enum sound_play_status play_status; void sound_progress_set_marker(int val) { gdk_threads_enter(); kry_marker_set_value(app.ui.marker_current, val); gdk_threads_leave(); } void sound_progress_set_marker_poll(int val, int val_prev) { /* kryListIterator iter; app.ui.waves.GetIterator(&iter); while(struct waveform_info *info = iter.GetNext()) { kry_waveform_marker_draw_erase(GTK_WIDGET(info->waveform), val, val_prev); }*/ } gpointer sound_progress_thread(gpointer data) { int progress_local = -1; g_mutex_lock(progress_mutex); while(1) { if(soundserver_get_position_type() == POSITION_POLL) { int pos_prev = progress_local; kry_sleep(30); enum soundserver_position_rv rv = soundserver_get_position(&progress_local); if(rv == SOUNDSERVER_OK) { sound_progress_set_marker_poll(progress_local, pos_prev); } else if(rv == SOUNDSERVER_NOT_RUNNING) { if(pos_prev != -1) sound_progress_set_marker_poll(-1, pos_prev); //g_warning("waiting"); g_cond_wait(progress_change, progress_mutex); //g_warning("done"); } } else { g_cond_wait(progress_change, progress_mutex); g_mutex_lock(progress_data_mutex); progress_local = progress; g_mutex_unlock(progress_data_mutex); sound_progress_set_marker(progress_local); } } return NULL; } struct sound_info *sound_open(char *file, char **error) { SF_INFO sfinfo; SNDFILE *snd;; short *buffer; unsigned char **wavegraph; int frames; int seconds = 0; int total_length; unsigned char max_pos = 0; unsigned char max_neg = 0; int idx; unsigned short short_max = 0; int wave_index = 0; struct sound_info *sound_info = kry_new(struct sound_info, 1); int vals_per_sec = 0; int padded_count = 0; short *max; short *min; double data_step; gboolean need_rounding = FALSE; int last_time = 0; GTimer *timer = NULL; char *file_locale = g_locale_from_utf8(file, -1, NULL, NULL, NULL); snd = sf_open(file_locale, SFM_READ, &sfinfo); g_free(file_locale); mute[0] = 0; mute[1] = 0; short_max = ((unsigned short) ~short_max / 2); if(snd == NULL) { if(error) *error = kry_strdup(sf_strerror(NULL)); kry_free(sound_info); return NULL; } vals_per_sec = 1000; sound_info->vals_per_sec = vals_per_sec; sound_info->channels = sfinfo.channels; sound_info->refcount = 0; sound_info->snd = snd; sound_info->sfinfo = sfinfo; total_length = (int) (sfinfo.frames / sfinfo.samplerate); if(sfinfo.frames % sfinfo.samplerate != 0) total_length++; /* we allocate double the data because we store negative vals and positive vals separately */ wavegraph = (unsigned char **) kry_malloc(sfinfo.channels * sizeof(int)); for(idx = 0; idx < sfinfo.channels; idx++) { wavegraph[idx] = (unsigned char *) kry_malloc(total_length * vals_per_sec * 2); memset(wavegraph[idx], 0, total_length + vals_per_sec * 2); } sound_info->length_seconds = total_length; sound_info->filename = kry_strdup(file); sound_info->waveform_data = (char **) wavegraph; char *peakfile = kry_path_replace_ext(sound_info->filename, "sabbu_pk"); if(kry_peakfile_read(peakfile, sound_info)) { kry_free(peakfile); return sound_info; } kry_free(peakfile); max = (short *) kry_malloc(sfinfo.channels * sizeof(short)); min = (short *) kry_malloc(sfinfo.channels * sizeof(short)); buffer = (short *) kry_malloc(sfinfo.samplerate * sfinfo.channels * sizeof(short)); data_step = sfinfo.samplerate * sfinfo.channels / (double) vals_per_sec; if(data_step - (int) data_step > 0.0001) need_rounding = TRUE; /* read and sample one second at a time*/ while(1) { frames = (int) sf_readf_short(snd, buffer, sfinfo.samplerate); if(frames == 0) break; if((frames * sfinfo.channels) % vals_per_sec != 0) { padded_count++; memset((char *) buffer + frames * sfinfo.channels * sizeof(short), 0, sfinfo.samplerate * sfinfo.channels * sizeof(short) - frames * sfinfo.channels * sizeof(short)); frames = sfinfo.samplerate; } for(int interval = 0; interval < vals_per_sec; interval++) { double interval_start_flt = interval * data_step; double interval_end_flt = (interval + 1) * data_step; int interval_start = (int) interval_start_flt; int interval_end = (int) interval_end_flt; if(interval_start_flt - (int) interval_start_flt > 0.000001) interval_start -= sfinfo.channels; if(interval_end_flt - (int) interval_end_flt > 0.000001) interval_end += sfinfo.channels; memset(max, 0, sfinfo.channels * sizeof(short)); memset(min, 0, sfinfo.channels * sizeof(short)); for(idx = interval_start; idx < interval_end; idx++) { if(buffer[idx] > max[idx % sfinfo.channels]) max[idx % sfinfo.channels] = buffer[idx]; else if(buffer[idx] < min[idx % sfinfo.channels]) min[idx % sfinfo.channels] = buffer[idx]; } for(idx = 0; idx < sfinfo.channels; idx++) { wavegraph[idx][wave_index] = (char) ((((double) max[idx]) / short_max) * 255); wavegraph[idx][wave_index+1] = (char) ((0-(((double) min[idx])) / short_max) * 255); if(wavegraph[idx][wave_index] > max_pos) max_pos = wavegraph[idx][wave_index]; if(wavegraph[idx][wave_index+1] > max_neg) max_neg = wavegraph[idx][wave_index+1]; } wave_index += 2; } seconds++; if(timer == NULL || g_timer_elapsed(timer, NULL) > 3 || (seconds - last_time) > total_length / 10) { gui_status_bar_set_progress(app.ui.status_bar, (double) seconds / total_length, TRUE); if(!timer) timer = g_timer_new(); g_timer_start(timer); last_time = seconds; } } g_timer_destroy(timer); sound_info->max_pos = max_pos; sound_info->max_neg = max_neg; if(padded_count > 1) { g_warning("More than one (%d) padding performed: Suspicious.", padded_count); } kry_free(buffer); if(app.opts.peak_files) gui_peakfile_save(sound_info->filename, sound_info); return sound_info; } void sound_signal_pos_change(long pos) { g_mutex_lock(progress_data_mutex); if(pos != -1 && progress != -1 && pos - progress <= 30) { g_mutex_unlock(progress_data_mutex); return; } progress = pos; g_mutex_unlock(progress_data_mutex); g_cond_broadcast(progress_change); } long sound_buffer_fill(void *pSoundBuffer,long bufferLen, struct sound_play_data *data) { long toRead; long read; int divisor; if(data->play_speed == 25) divisor = 4; else if(data->play_speed == 50) divisor = 2; else divisor = 1; // false = AUTOMATIC, true = MANUAL enum sound_position_type pos_type = soundserver_get_position_type(); g_mutex_lock(sound_data_mutex); if(data->frame_end != -1 && ((data->frame_end - data->frame_current) * data->sfinfo->channels * divisor) < bufferLen / 2) { if(pos_type == POSITION_AUTOMATIC) data->prevent_change = TRUE; toRead = (data->frame_end - data->frame_current) * data->sfinfo->channels; if(pos_type == POSITION_AUTOMATIC) sound_signal_pos_change(-1); } else { toRead = bufferLen / (2 * divisor); if(pos_type == POSITION_AUTOMATIC) sound_signal_pos_change((long) ((((double) data->frame_current / data->sfinfo->samplerate)) * 1000)); } if(data->sfinfo->channels == 2 && toRead % 2 != 0) toRead--; read = (long) sf_read_short(data->snd, (short *) pSoundBuffer, toRead); memset((char *) pSoundBuffer + read*2, 0, bufferLen - read * 2); ///g_warning("toRead/read: %d, %d", toRead, read); if(data->play_speed != 100) { short *buf = (short *) pSoundBuffer; for(int i = (bufferLen / 2) - 1; i >= 0; i -= divisor) { for(int j = 0; j < divisor; j++) { buf[i - j] = buf[i / divisor]; } } } if(pos_type == POSITION_AUTOMATIC && read < toRead) { g_mutex_lock(progress_data_mutex); progress = -1; g_mutex_unlock(progress_data_mutex); g_cond_broadcast(progress_change); } data->frame_current += (read / data->sfinfo->channels); g_mutex_unlock(sound_data_mutex); return read * 2; } void sound_marker_end_changed(KryMarker *marker, long position_old, struct sound_play_data *data) { g_mutex_lock(sound_data_mutex); if(!data->prevent_change) data->frame_end = (long) ((data->sfinfo->samplerate / 1000.0) * kry_marker_get_value(marker)); g_mutex_unlock(sound_data_mutex); } /* * **NOTE** If this function is invoked from a GTK callback, * The GTK global lock *MUST* be unlocked beforehand (gdk_threads_leave()) or a deadlock might occur. */ void sound_play(SNDFILE *snd, SF_INFO *sfinfo, KryMarker *marker_start, KryMarker *marker_end) { struct sound_play_data *data = kry_new0(struct sound_play_data); data->marker_start = marker_start; data->marker_end = marker_end; data->frame_end = -1; data->play_speed = app.ui.play_speed; if(kry_marker_get_value(marker_start) == -1) { kry_free(data); return; } data->mili_start = kry_marker_get_value(marker_start); data->frame_start = (long) ((sfinfo->samplerate / 1000.0) * kry_marker_get_value(marker_start)); if(kry_marker_get_value(marker_end) != -1) data->frame_end = (long) ((sfinfo->samplerate / 1000.0) * kry_marker_get_value(marker_end)); data->sfinfo = sfinfo; data->snd = snd; data->frame_current = data->frame_start; if(sf_seek(data->snd, data->frame_start, SEEK_SET) == -1) { GtkDialog *dialog; gdk_threads_enter(); // TODO: belongs in UI code dialog = GTK_DIALOG(gtk_message_dialog_new(GTK_WINDOW(app.ui.window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Event located beyond end of audio file"))); gtk_dialog_run(dialog); gtk_widget_destroy(GTK_WIDGET(dialog)); gdk_threads_leave(); return; } //g_warning("play sound"); g_object_ref(marker_start); g_object_ref(marker_end); g_signal_connect(marker_end, "value-changed", G_CALLBACK(sound_marker_end_changed), data); soundserver_stop(); if(!soundserver_open((USER_CALLBACK) sound_buffer_fill, sfinfo->channels, sfinfo->samplerate)) { gdk_threads_enter(); gui_error(_("Sound device could not be set up")); gdk_threads_leave(); return; } g_cond_broadcast(progress_change); if(mute[0]) soundserver_mute(0); if(mute[1]) soundserver_mute(1); soundserver_play(data); } void sound_stop() { soundserver_stop(); sound_signal_pos_change(-1); } void sound_mute(int channel, gboolean doMute) { mute[channel] = doMute; } void sound_init() { if(!progress_mutex) { GError *error; /* TODO: maybe this belongs elsewhere */ progress_mutex = g_mutex_new(); progress_data_mutex = g_mutex_new(); progress_change = g_cond_new(); sound_data_mutex = g_mutex_new(); g_thread_create(sound_progress_thread, NULL, FALSE, &error); } }