editor/bass-sys/linux/bass24/custloop/custloop.c

272 lines
8.6 KiB
C
Raw Normal View History

2021-01-08 03:37:50 +00:00
/*
BASS custom looping example
Copyright (c) 2004-2014 Un4seen Developments Ltd.
*/
#include <gtk/gtk.h>
#include <glib/gthread.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>
#include <math.h>
#include "bass.h"
#define WIDTH 600 // display width
#define HEIGHT 201 // height (odd number for centre line)
#pragma pack(1)
typedef struct {
BYTE rgbRed, rgbGreen, rgbBlue;
} RGB;
#pragma pack()
GtkWidget *win = 0;
GThread *scanthread = 0;
BOOL killscan = FALSE;
DWORD chan;
DWORD bpp; // bytes per pixel
QWORD loop[2] = { 0 }; // loop start & end
HSYNC lsync; // looping sync
GtkWidget *waveda;
GdkPixbuf *wavepb;
RGB palette[HEIGHT / 2 + 1];
// display error messages
void Error(const char *es)
{
GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s\n(error code: %d)", es, BASS_ErrorGetCode());
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
}
void WindowDestroy(GtkObject *obj, gpointer data)
{
gtk_main_quit();
}
void CALLBACK LoopSyncProc(HSYNC handle, DWORD channel, DWORD data, void *user)
{
if (!BASS_ChannelSetPosition(channel, loop[0], BASS_POS_BYTE)) // try seeking to loop start
BASS_ChannelSetPosition(channel, 0, BASS_POS_BYTE); // failed, go to start of file instead
}
void SetLoopStart(QWORD pos)
{
loop[0] = pos;
}
void SetLoopEnd(QWORD pos)
{
loop[1] = pos;
BASS_ChannelRemoveSync(chan, lsync); // remove old sync
lsync = BASS_ChannelSetSync(chan, BASS_SYNC_POS | BASS_SYNC_MIXTIME, loop[1], LoopSyncProc, 0); // set new sync
}
// scan the peaks
void *ScanPeaks(void *p)
{
DWORD decoder = (uintptr_t)p;
RGB *wavebuf = (RGB*)gdk_pixbuf_get_pixels(wavepb);
DWORD pos = 0;
float spp = BASS_ChannelBytes2Seconds(decoder, bpp); // seconds per pixel
while (!killscan) {
float peak[2];
if (spp > 1) { // more than 1 second per pixel, break it down...
float todo = spp;
peak[1] = peak[0] = 0;
do {
float level[2], step = (todo < 1 ? todo : 1);
BASS_ChannelGetLevelEx(decoder, level, step, BASS_LEVEL_STEREO); // scan peaks
if (peak[0] < level[0]) peak[0] = level[0];
if (peak[1] < level[1]) peak[1] = level[1];
todo -= step;
} while (todo > 0);
} else
BASS_ChannelGetLevelEx(decoder, peak, spp, BASS_LEVEL_STEREO); // scan peaks
{
DWORD a;
for (a = 0; a < peak[0] * (HEIGHT / 2); a++)
wavebuf[(HEIGHT / 2 - 1 - a) * WIDTH + pos] = palette[1 + a]; // draw left peak
for (a = 0; a < peak[1] * (HEIGHT / 2); a++)
wavebuf[(HEIGHT / 2 + 1 + a) * WIDTH + pos] = palette[1 + a]; // draw right peak
}
pos++;
if (pos >= WIDTH) break; // reached end of display
if (!BASS_ChannelIsActive(decoder)) break; // reached end of channel
}
if (!killscan) {
DWORD size;
BASS_ChannelSetPosition(decoder, (QWORD)-1, BASS_POS_BYTE | BASS_POS_SCAN); // build seek table (scan to end)
size = BASS_ChannelGetAttributeEx(decoder, BASS_ATTRIB_SCANINFO, 0, 0); // get seek table size
if (size) { // got it
void *info = malloc(size); // allocate a buffer
BASS_ChannelGetAttributeEx(decoder, BASS_ATTRIB_SCANINFO, info, size); // get the seek table
BASS_ChannelSetAttributeEx(chan, BASS_ATTRIB_SCANINFO, info, size); // apply it to the playback channel
free(info);
}
}
BASS_StreamFree(decoder); // free the decoder
return NULL;
}
gboolean FileExtensionFilter(const GtkFileFilterInfo *info, gpointer data)
{
return !regexec((regex_t*)data, info->filename, 0, NULL, 0);
}
// select a file to play, and start scanning it
BOOL PlayFile()
{
BOOL ret = FALSE;
regex_t fregex;
GtkWidget *filesel; // file selector
GtkFileFilter *filter;
filesel = gtk_file_chooser_dialog_new("Open File", GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_OPEN,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
filter = gtk_file_filter_new();
gtk_file_filter_set_name(filter, "Playable files");
regcomp(&fregex, "\\.(mo3|xm|mod|s3m|it|umx|mp[1-3]|ogg|wav|aif)$", REG_ICASE | REG_NOSUB | REG_EXTENDED);
gtk_file_filter_add_custom(filter, GTK_FILE_FILTER_FILENAME, FileExtensionFilter, &fregex, NULL);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filesel), filter);
filter = gtk_file_filter_new();
gtk_file_filter_set_name(filter, "All files");
gtk_file_filter_add_pattern(filter, "*");
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filesel), filter);
if (gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) {
char *file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel));
gtk_widget_hide(filesel);
if (!(chan = BASS_StreamCreateFile(FALSE, file, 0, 0, 0))
&& !(chan = BASS_MusicLoad(FALSE, file, 0, 0, BASS_MUSIC_RAMP | BASS_MUSIC_POSRESET | BASS_MUSIC_PRESCAN, 1))) {
Error("Can't play file");
} else {
// create the bitmap
wavepb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, WIDTH, HEIGHT);
{ // setup palette
RGB *pal = (RGB*)palette;
int a;
memset(palette, 0, sizeof(palette));
for (a = 1; a <= HEIGHT / 2; a++) {
pal[a].rgbRed = (255 * a) / (HEIGHT / 2);
pal[a].rgbGreen = 255 - pal[a].rgbRed;
}
}
bpp = BASS_ChannelGetLength(chan, BASS_POS_BYTE) / WIDTH; // bytes per pixel
{
DWORD bpp1 = BASS_ChannelSeconds2Bytes(chan, 0.001); // minimum 1ms per pixel
if (bpp < bpp1) bpp = bpp1;
}
BASS_ChannelSetSync(chan, BASS_SYNC_END | BASS_SYNC_MIXTIME, 0, LoopSyncProc, 0); // set sync to loop at end
BASS_ChannelPlay(chan, FALSE); // start playing
{ // create another channel to scan
DWORD chan2 = BASS_StreamCreateFile(FALSE, file, 0, 0, BASS_STREAM_DECODE);
if (!chan2) chan2 = BASS_MusicLoad(FALSE, file, 0, 0, BASS_MUSIC_DECODE, 1);
#if GLIB_CHECK_VERSION(2,32,0)
scanthread = g_thread_new(NULL, ScanPeaks, (void*)(uintptr_t)chan2); // start scanning in a new thread
#else
scanthread = g_thread_create(ScanPeaks, (void*)(uintptr_t)chan2, TRUE, NULL); // start scanning in a new thread
#endif
}
ret = TRUE;
}
g_free(file);
}
gtk_widget_destroy(filesel);
return ret;
}
void WindowButtonRelease(GtkWidget *obj, GdkEventButton *event, gpointer data)
{
if (event->type == GDK_BUTTON_RELEASE) {
if (event->button == 1) // set loop start
SetLoopStart(event->x * bpp);
else if (event->button == 3) // set loop end
SetLoopEnd(event->x * bpp);
}
}
void DrawTimeLine(QWORD pos, DWORD col, DWORD y)
{
GdkGC *gc = waveda->style->fg_gc[GTK_WIDGET_STATE(waveda)];
DWORD wpos = pos / bpp;
GdkColor c = { 0,(col & 0xff) << 8,col & 0xff00,(col >> 8) & 0xff00 };
gdk_gc_set_rgb_fg_color(gc, &c);
gdk_draw_line(waveda->window, gc, wpos, 0, wpos, HEIGHT);
}
gboolean AreaExpose(GtkWidget *widget, GdkEventExpose *event, gpointer user)
{
GdkGC *gc = widget->style->fg_gc[GTK_WIDGET_STATE(waveda)];
GdkGCValues gcsave;
gdk_gc_get_values(gc, &gcsave);
gdk_draw_pixbuf(widget->window, gc, wavepb, 0, 0, 0, 0, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
DrawTimeLine(loop[0], 0xffff00, 12); // loop start
DrawTimeLine(loop[1], 0x00ffff, 24); // loop end
DrawTimeLine(BASS_ChannelGetPosition(chan, BASS_POS_BYTE), 0xffffff, 0); // current pos
gdk_gc_set_values(gc, &gcsave, GDK_GC_FOREGROUND);
return FALSE;
}
gboolean TimerProc(gpointer data)
{
// refresh window
gtk_widget_queue_draw(waveda);
return TRUE;
}
int main(int argc, char* argv[])
{
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
gtk_init(&argc, &argv);
// check the correct BASS was loaded
if (HIWORD(BASS_GetVersion()) != BASSVERSION) {
Error("An incorrect version of BASS was loaded");
return 0;
}
// initialize BASS
if (!BASS_Init(-1, 44100, 0, win, NULL)) {
Error("Can't initialize device");
return 0;
}
if (!PlayFile()) { // start a file playing
BASS_Free();
return 0;
}
// create the window
win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER);
gtk_window_set_resizable(GTK_WINDOW(win), FALSE);
gtk_window_set_title(GTK_WINDOW(win), "BASS custom looping example (left-click to set loop start, right-click to set end)");
g_signal_connect(win, "destroy", GTK_SIGNAL_FUNC(WindowDestroy), NULL);
GtkWidget *ebox = gtk_event_box_new();
gtk_container_add(GTK_CONTAINER(win), ebox);
g_signal_connect(ebox, "button-release-event", GTK_SIGNAL_FUNC(WindowButtonRelease), NULL);
waveda = gtk_drawing_area_new();
gtk_widget_set_size_request(waveda, WIDTH, HEIGHT);
gtk_container_add(GTK_CONTAINER(ebox), waveda);
g_signal_connect(waveda, "expose-event", GTK_SIGNAL_FUNC(AreaExpose), NULL);
// setup update timer (10hz)
g_timeout_add(100, TimerProc, NULL);
gtk_widget_show_all(win);
gtk_main();
killscan = TRUE;
g_thread_join(scanthread); // wait for the scan thread
g_object_unref(wavepb);
BASS_Free();
return 0;
}