316 lines
10 KiB
C
316 lines
10 KiB
C
|
/*
|
||
|
BASS 3D test
|
||
|
Copyright (c) 1999-2012 Un4seen Developments Ltd.
|
||
|
*/
|
||
|
|
||
|
#include <gtk/gtk.h>
|
||
|
#include <glade/glade.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <math.h>
|
||
|
#include <regex.h>
|
||
|
#include "bass.h"
|
||
|
|
||
|
// path to glade file
|
||
|
#ifndef GLADE_PATH
|
||
|
#define GLADE_PATH ""
|
||
|
#endif
|
||
|
|
||
|
GladeXML *glade;
|
||
|
GtkWidget *win = 0;
|
||
|
GtkWidget *filesel;
|
||
|
|
||
|
// channel (sample/music) info structure
|
||
|
typedef struct {
|
||
|
DWORD channel; // the channel
|
||
|
BASS_3DVECTOR pos, vel; // position,velocity
|
||
|
} Channel;
|
||
|
|
||
|
Channel *chans = NULL; // the channels
|
||
|
int chanc = 0, chan = -1; // number of channels, current channel
|
||
|
|
||
|
#define TIMERPERIOD 50 // timer period (ms)
|
||
|
#define MAXDIST 50 // maximum distance of the channels (m)
|
||
|
#define SPEED 12 // speed of the channels' movement (m/s)
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
|
||
|
#define GetWidget(id) glade_xml_get_widget(glade,id)
|
||
|
|
||
|
void WindowDestroy(GtkObject *obj, gpointer data)
|
||
|
{
|
||
|
gtk_main_quit();
|
||
|
}
|
||
|
|
||
|
// Update the button states
|
||
|
void UpdateButtons()
|
||
|
{
|
||
|
gtk_widget_set_sensitive(GetWidget("remove"), chan == -1 ? FALSE : TRUE);
|
||
|
gtk_widget_set_sensitive(GetWidget("play"), chan == -1 ? FALSE : TRUE);
|
||
|
gtk_widget_set_sensitive(GetWidget("stop"), chan == -1 ? FALSE : TRUE);
|
||
|
gtk_widget_set_sensitive(GetWidget("movex"), chan == -1 ? FALSE : TRUE);
|
||
|
gtk_widget_set_sensitive(GetWidget("movez"), chan == -1 ? FALSE : TRUE);
|
||
|
gtk_widget_set_sensitive(GetWidget("movereset"), chan == -1 ? FALSE : TRUE);
|
||
|
if (chan != -1) {
|
||
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(GetWidget("movex")), fabs(chans[chan].vel.x));
|
||
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(GetWidget("movez")), fabs(chans[chan].vel.z));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gboolean ListSelectionChange(GtkTreeView *treeview, gpointer data)
|
||
|
{ // the selected channel has (probably) changed
|
||
|
GtkTreeSelection *ts;
|
||
|
GtkTreeModel *tm;
|
||
|
GtkTreeIter it;
|
||
|
ts = gtk_tree_view_get_selection(GTK_TREE_VIEW(GetWidget("channels")));
|
||
|
if (gtk_tree_selection_get_selected(ts, &tm, &it)) {
|
||
|
char *rows = gtk_tree_model_get_string_from_iter(tm, &it);
|
||
|
chan = atoi(rows);
|
||
|
g_free(rows);
|
||
|
} else
|
||
|
chan = -1;
|
||
|
UpdateButtons();
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
gboolean FileExtensionFilter(const GtkFileFilterInfo *info, gpointer data)
|
||
|
{
|
||
|
return !regexec((regex_t*)data, info->filename, 0, NULL, 0);
|
||
|
}
|
||
|
|
||
|
void AddClicked(GtkButton *obj, gpointer data)
|
||
|
{ // add a channel
|
||
|
int resp = gtk_dialog_run(GTK_DIALOG(filesel));
|
||
|
gtk_widget_hide(filesel);
|
||
|
if (resp == GTK_RESPONSE_ACCEPT) {
|
||
|
char *file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel));
|
||
|
DWORD newchan;
|
||
|
// Load a music or sample from "file"
|
||
|
if ((newchan = BASS_MusicLoad(FALSE, file, 0, 0, BASS_MUSIC_RAMP | BASS_SAMPLE_LOOP | BASS_SAMPLE_3D, 1))
|
||
|
|| (newchan = BASS_SampleLoad(FALSE, file, 0, 0, 1, BASS_SAMPLE_LOOP | BASS_SAMPLE_3D | BASS_SAMPLE_MONO))) {
|
||
|
Channel *c;
|
||
|
chanc++;
|
||
|
chans = (Channel*)realloc((void*)chans, chanc * sizeof(Channel));
|
||
|
c = chans + chanc - 1;
|
||
|
memset(c, 0, sizeof(Channel));
|
||
|
c->channel = newchan;
|
||
|
BASS_SampleGetChannel(newchan, FALSE); // initialize sample channel
|
||
|
{ // add it to the list
|
||
|
GtkTreeView *tree = GTK_TREE_VIEW(GetWidget("channels"));
|
||
|
GtkListStore *tm = GTK_LIST_STORE(gtk_tree_view_get_model(tree));
|
||
|
GtkTreeIter it;
|
||
|
gtk_list_store_append(tm, &it);
|
||
|
gtk_list_store_set(tm, &it, 0, strrchr(file, '/') + 1, -1);
|
||
|
}
|
||
|
} else
|
||
|
Error("Can't load file (note samples must be mono)");
|
||
|
g_free(file);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void RemoveClicked(GtkButton *obj, gpointer data)
|
||
|
{ // remove a channel
|
||
|
GtkTreeModel *tm = gtk_tree_view_get_model(GTK_TREE_VIEW(GetWidget("channels")));
|
||
|
GtkTreeIter it;
|
||
|
if (gtk_tree_model_iter_nth_child(tm, &it, NULL, chan)) {
|
||
|
Channel *c = chans + chan;
|
||
|
// free both MOD and stream, it must be one of them! :)
|
||
|
BASS_SampleFree(c->channel);
|
||
|
BASS_MusicFree(c->channel);
|
||
|
chanc--;
|
||
|
memmove(c, c + 1, (chanc - chan) * sizeof(Channel));
|
||
|
gtk_list_store_remove(GTK_LIST_STORE(tm), &it);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void PlayClicked(GtkButton *obj, gpointer data)
|
||
|
{
|
||
|
BASS_ChannelPlay(chans[chan].channel, FALSE);
|
||
|
}
|
||
|
|
||
|
void StopClicked(GtkButton *obj, gpointer data)
|
||
|
{
|
||
|
BASS_ChannelPause(chans[chan].channel);
|
||
|
}
|
||
|
|
||
|
void MoveXChanged(GtkSpinButton *spinbutton, gpointer data)
|
||
|
{ // X velocity
|
||
|
float value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(GetWidget("movex")));
|
||
|
if (fabs(chans[chan].vel.x) != value) chans[chan].vel.x = value;
|
||
|
}
|
||
|
|
||
|
void MoveZChanged(GtkSpinButton *spinbutton, gpointer data)
|
||
|
{ // Y velocity
|
||
|
float value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(GetWidget("movez")));
|
||
|
if (fabs(chans[chan].vel.z) != value) chans[chan].vel.z = value;
|
||
|
}
|
||
|
|
||
|
void MoveResetClicked(GtkButton *obj, gpointer data)
|
||
|
{ // reset the position and velocity to 0
|
||
|
memset(&chans[chan].pos, 0, sizeof(chans[chan].pos));
|
||
|
memset(&chans[chan].vel, 0, sizeof(chans[chan].vel));
|
||
|
UpdateButtons();
|
||
|
}
|
||
|
|
||
|
void RolloffChanged(GtkRange *range, gpointer data)
|
||
|
{
|
||
|
// change the rolloff factor
|
||
|
double value = gtk_range_get_value(range);
|
||
|
BASS_Set3DFactors(-1, pow(2, (value - 10) / 5.0), -1);
|
||
|
}
|
||
|
|
||
|
void DopplerChanged(GtkRange *range, gpointer data)
|
||
|
{
|
||
|
// change the doppler factor
|
||
|
double value = gtk_range_get_value(range);
|
||
|
BASS_Set3DFactors(-1, -1, pow(2, (value - 10) / 5.0));
|
||
|
}
|
||
|
|
||
|
gboolean Update(gpointer data)
|
||
|
{
|
||
|
int c, x, y, cx, cy;
|
||
|
GtkWidget *dc = GetWidget("drawingarea1");
|
||
|
GdkGC *gc = dc->style->fg_gc[GTK_WIDGET_STATE(dc)];
|
||
|
GdkGCValues gcsave;
|
||
|
|
||
|
gdk_gc_get_values(gc, &gcsave);
|
||
|
|
||
|
cx = dc->allocation.width / 2;
|
||
|
cy = dc->allocation.height / 2;
|
||
|
|
||
|
{ // clear the display
|
||
|
GdkColor c = { 0,0xffff,0xffff,0xffff };
|
||
|
gdk_gc_set_rgb_fg_color(gc, &c);
|
||
|
gdk_draw_rectangle(dc->window, gc, TRUE, 0, 0, dc->allocation.width, dc->allocation.height);
|
||
|
}
|
||
|
|
||
|
{ // Draw the listener
|
||
|
GdkColor c = { 0,0x8000,0x8000,0x8000 };
|
||
|
gdk_gc_set_rgb_fg_color(gc, &c);
|
||
|
gdk_draw_arc(dc->window, gc, TRUE, cx - 4, cy - 4, 8, 8, 0, 360 * 64);
|
||
|
}
|
||
|
|
||
|
for (c = 0; c < chanc; c++) {
|
||
|
// If the channel's playing then update it's position
|
||
|
if (BASS_ChannelIsActive(chans[c].channel) == BASS_ACTIVE_PLAYING) {
|
||
|
// Check if channel has reached the max distance
|
||
|
if (chans[c].pos.z >= MAXDIST || chans[c].pos.z <= -MAXDIST)
|
||
|
chans[c].vel.z = -chans[c].vel.z;
|
||
|
if (chans[c].pos.x >= MAXDIST || chans[c].pos.x <= -MAXDIST)
|
||
|
chans[c].vel.x = -chans[c].vel.x;
|
||
|
// Update channel position
|
||
|
chans[c].pos.z += chans[c].vel.z * TIMERPERIOD / 1000;
|
||
|
chans[c].pos.x += chans[c].vel.x * TIMERPERIOD / 1000;
|
||
|
BASS_ChannelSet3DPosition(chans[c].channel, &chans[c].pos, NULL, &chans[c].vel);
|
||
|
}
|
||
|
{ // Draw the channel position indicator
|
||
|
static GdkColor cols[2] = { {0,0xffff,0xc000,0xc000},{0,0xffff,0,0} };
|
||
|
gdk_gc_set_rgb_fg_color(gc, &cols[chan == c]);
|
||
|
x = cx + (int)((cx - 10) * chans[c].pos.x / MAXDIST);
|
||
|
y = cy - (int)((cy - 10) * chans[c].pos.z / MAXDIST);
|
||
|
gdk_draw_arc(dc->window, gc, TRUE, x - 4, y - 4, 8, 8, 0, 360 * 64);
|
||
|
}
|
||
|
}
|
||
|
// Apply the 3D changes
|
||
|
BASS_Apply3D();
|
||
|
|
||
|
gdk_gc_set_values(gc, &gcsave, GDK_GC_FOREGROUND);
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
int main(int argc, char* argv[])
|
||
|
{
|
||
|
regex_t fregex[2];
|
||
|
|
||
|
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 the output device with 3D support
|
||
|
if (!BASS_Init(-1, 44100, BASS_DEVICE_3D, NULL, NULL)) {
|
||
|
Error("Can't initialize device");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
{
|
||
|
BASS_INFO i;
|
||
|
BASS_GetInfo(&i);
|
||
|
if (i.speakers > 2) {
|
||
|
GtkWidget *dialog = gtk_message_dialog_new(NULL, 0,
|
||
|
GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "Multiple speakers were detected. Would you like to use them?");
|
||
|
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_NO)
|
||
|
BASS_SetConfig(BASS_CONFIG_3DALGORITHM, BASS_3DALG_OFF);
|
||
|
gtk_widget_destroy(dialog);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Use meters as distance unit, real world rolloff, real doppler effect
|
||
|
BASS_Set3DFactors(1, 1, 1);
|
||
|
|
||
|
// initialize GUI
|
||
|
glade = glade_xml_new(GLADE_PATH"3dtest.glade", NULL, NULL);
|
||
|
if (!glade) return 0;
|
||
|
win = GetWidget("window1");
|
||
|
if (!win) return 0;
|
||
|
glade_xml_signal_autoconnect(glade);
|
||
|
g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(GetWidget("channels"))), "changed", G_CALLBACK(ListSelectionChange), NULL);
|
||
|
|
||
|
{ // setup list
|
||
|
GtkTreeView *list = GTK_TREE_VIEW(GetWidget("channels"));
|
||
|
GtkTreeViewColumn *col = gtk_tree_view_column_new();
|
||
|
gtk_tree_view_append_column(list, col);
|
||
|
GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
|
||
|
gtk_tree_view_column_pack_start(col, renderer, TRUE);
|
||
|
gtk_tree_view_column_add_attribute(col, renderer, "text", 0);
|
||
|
GtkListStore *liststore = gtk_list_store_new(1, G_TYPE_STRING);
|
||
|
gtk_tree_view_set_model(list, GTK_TREE_MODEL(liststore));
|
||
|
g_object_unref(liststore);
|
||
|
}
|
||
|
|
||
|
{ // initialize 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, "Streamable files (wav/aif/mp3/mp2/mp1/ogg)");
|
||
|
regcomp(&fregex[0], "\\.(mp[1-3]|ogg|wav|aif)$", REG_ICASE | REG_NOSUB | REG_EXTENDED);
|
||
|
gtk_file_filter_add_custom(filter, GTK_FILE_FILTER_FILENAME, FileExtensionFilter, &fregex[0], NULL);
|
||
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filesel), filter);
|
||
|
filter = gtk_file_filter_new();
|
||
|
gtk_file_filter_set_name(filter, "MOD music files (mo3/xm/mod/s3m/it/mtm/umx)");
|
||
|
regcomp(&fregex[1], "\\.(mo3|xm|mod|s3m|it|umx)$", REG_ICASE | REG_NOSUB | REG_EXTENDED);
|
||
|
gtk_file_filter_add_custom(filter, GTK_FILE_FILTER_FILENAME, FileExtensionFilter, &fregex[1], 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);
|
||
|
}
|
||
|
|
||
|
g_timeout_add(TIMERPERIOD, Update, NULL);
|
||
|
|
||
|
UpdateButtons();
|
||
|
|
||
|
gtk_main();
|
||
|
|
||
|
gtk_widget_destroy(filesel);
|
||
|
regfree(&fregex[0]);
|
||
|
regfree(&fregex[1]);
|
||
|
|
||
|
BASS_Free(); // close output
|
||
|
|
||
|
return 0;
|
||
|
}
|