Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[programs/modplay] Add .mod visualization #159

Merged
merged 1 commit into from
Jul 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 242 additions & 33 deletions programs/modplay/modplay.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ tag_type empty_tag =

modcontext modctx;

#define NUM_MODFILES 5

uint8_t *modfiles[] = {
modfile_axelf,
modfile_breath,
Expand All @@ -35,10 +37,13 @@ uint8_t *modfiles[] = {
modfile_skogen10
};

int modfile_sizes[5];
int modfile_sizes[NUM_MODFILES];

static bool select_modfile(int current_mod_file);
static int initialize_mod_player(uint8_t *modfile, int length);
static void initialize_tracker_buffer_state(tracker_buffer_state *trackbuf_state);
static void print_mod_name(void);
static uint16_t abs(int16_t value);

int main(void)
{
Expand All @@ -65,14 +70,15 @@ int main(void)
width: 80,
height: 50,
depth: 4,
activate: TRUE
activate: TRUE,
enable_buffer: TRUE
};

console_open(&console_structure, ipc_console_attribute);
console_use_keyboard(&console_structure, TRUE, CONSOLE_KEYBOARD_NORMAL);
console_clear(&console_structure);
console_print(&console_structure,
"Module player\n");
console_cursor_move(&console_structure, 80, 50);
console_buffer_print_formatted(&console_structure, 0, 0, 0x1F, "%-80s",
" [\xFA] chaos Module Player");

if (sound_init(&ipc_structure, &empty_tag) != SOUND_RETURN_SUCCESS)
{
Expand All @@ -97,21 +103,147 @@ int main(void)

system_call_process_parent_unblock();

uint8_t *modfile = modfiles[0];
int modfile_size = modfile_sizes[0];
// The index of the .mod file currently playing
int current_mod_file = 0;
int current_mod_file_selecting = 0;

if (!initialize_mod_player(modfile, modfile_size))
if (!select_modfile(current_mod_file))
{
return -1;
}

print_mod_name();

int event_type;
keyboard_packet_type keyboard_packet;
tracker_buffer_state trackbuf_state;

initialize_tracker_buffer_state(&trackbuf_state);
int visualizations[12];
int channel_visualizations[NUMMAXCHANNELS];
int active_instruments[NUM_INSTRUMENTS];

memory_set_uint32_t((uint32_t *) &visualizations, 0, 12);
memory_set_uint32_t((uint32_t *) &channel_visualizations, 0, NUMMAXCHANNELS);

while (TRUE)
{
// From the hxcmod documentation (note that stereo 16bits is not entirely true, we override
// it to be 8-bit mono for now). Likewise, nbsample * 2 *2 presumes 16-bit stereo is being
// used.

// void hxcmod_fillbuffer( modcontext * modctx, unsigned short * outbuffer, unsigned long nbsample, tracker_buffer_state * trkbuf )

// - Generate and return the next samples chunk to outbuffer.
// nbsample specify the number of stereo 16bits samples you want.
// The output format is signed 44100Hz 16-bit Stereo PCM samples.
// The output buffer size in byte must be equal to ( nbsample * 2 * 2 ).
// The optional trkbuf parameter can be used to get detailed status of the player. Put NULL/0 is unused.

trackbuf_state.nb_of_state = 0;
hxcmod_fillbuffer(&modctx, sound_message->data, NUMBER_OF_SAMPLES, &trackbuf_state);

if (sound_play_stream(&ipc_structure, sound_message) !=
SOUND_RETURN_SUCCESS)
{
log_print(&log_structure, LOG_URGENCY_EMERGENCY,
"Could not play the sample as wanted.");
return -1;
}

// Print info about instruments/samples.
memory_set_uint32_t(active_instruments, 0, NUM_INSTRUMENTS);

for (int i = 0; i < modctx.number_of_channels; i++)
{
active_instruments[modctx.channels[i].sampnum] = i;
}

for (unsigned int i = 0; i < 31; i++)
{
int color = (active_instruments[i] > 0 ?
BRIGHT_INSTRUMENT_COLOR :
DEFAULT_INSTRUMENT_COLOR);

console_buffer_print_formatted(&console_structure, 54, 5 + i,
color, "%02d: %-22s",
i, trackbuf_state.instruments[i].name);
}

// Attempt to do some form of channel visualization
for (int i = 0; i < modctx.number_of_channels; i++)
{
track_state *track = &trackbuf_state.track_state_buf[0].tracks[i];
int sample_position = modctx.channels[i].samppos >> 10;

if (sample_position != 0 && modctx.channels[i].sampdata != NULL)
{
short pcm_data = modctx.channels[i].sampdata[sample_position] * track->cur_volume;

int previous = channel_visualizations[i];

// Special-case so that 32768 becomes 32767, i.e. small enough to fit in 15 bits.
channel_visualizations[i] = MIN_OF_TWO(abs(pcm_data), 32767);

// Try to "slow down" the changes in the visualizations a bit, to make it look
// less flickery. TODO: could try even more than 2 samples/channel to see if it
// looks even better.
channel_visualizations[i] = (channel_visualizations[i] + previous) / 2;
}
else
{
// Purpose of this: to avoid the bar going all the way down, just to go up again
// very shortly after; this can look quite flickery.
// int previous = channel_visualizations[i];
// channel_visualizations[i] = 0;
// channel_visualizations[i] = (channel_visualizations[i] + previous) / 2;

// Purpose of this: to avoid the bar going all the way down, just to go up again
// very shortly after; this can look quite flickery.
channel_visualizations[i] -= 1;

if (channel_visualizations[i] < 0)
{
channel_visualizations[i] = 0;
}
}
}

// Print one bar for each part of the spectrum
for (int i = 0; i < modctx.number_of_channels; i++)
{
char bar[32];

// 15 bits (0-32768) converted to 0-31 (= 5 bits) => shift away the right-most 10 bits.
// In practice, the values we get are often so small that it makes more sense to retain
// a few bits more (hence the >> 8) and filter away too-large values.
int channel_bar_length = (channel_visualizations[i] >> 8) & 0x1F;

for (int x = 0; x < channel_bar_length; x++)
{
bar[x] = 223; // ▀ in CP437
}

bar[channel_bar_length] = '\0';

console_buffer_print(&console_structure, 0, 5 + i, CONSOLE_BUFFER_COLOUR_CYAN, "[");
console_buffer_print_formatted(&console_structure, 1, 5 + i,
CONSOLE_BUFFER_COLOUR_BRIGHT_CYAN, "%-32s", bar);
console_buffer_print(&console_structure, 33, 5 + i, CONSOLE_BUFFER_COLOUR_CYAN, "]");
}

// Print a status line at the bottom of the screen
console_buffer_print_formatted(&console_structure, 0, 49, DEFAULT_COLOR,
" %d Channels, Pos %.3d, Pattern %.3d:%.2d, %.3d BPM, Speed %.3d",
trackbuf_state.track_state_buf[0].number_of_tracks,
trackbuf_state.track_state_buf[0].cur_pattern_table_pos,
trackbuf_state.track_state_buf[0].cur_pattern,
trackbuf_state.track_state_buf[0].cur_pattern_pos,
trackbuf_state.track_state_buf[0].bpm,
trackbuf_state.track_state_buf[0].speed
);

// Handle keypresses. This needs to happen late in the function
// to avoid keypresses having a "laggy" feel, since the audio
// playback part etc. in this function takes some time.
bool block = FALSE;
if (console_event_wait(&console_structure, &keyboard_packet, &event_type, block) == CONSOLE_RETURN_SUCCESS &&
event_type == CONSOLE_EVENT_KEYBOARD)
Expand All @@ -126,48 +258,101 @@ int main(void)
case '4':
case '5':
{
int selected_file = keyboard_packet.character_code[0] - '1';
current_mod_file = keyboard_packet.character_code[0] - '1';
current_mod_file_selecting = current_mod_file;

modfile = modfiles[selected_file];
modfile_size = modfile_sizes[selected_file];

if (!initialize_mod_player(modfile, modfile_size))
if (!select_modfile(current_mod_file))
{
return -1;
}

print_mod_name();
break;
}
}
}
}
else if (keyboard_packet.has_special_key && keyboard_packet.key_pressed)
{
switch (keyboard_packet.special_key)
{
case IPC_KEYBOARD_SPECIAL_KEY_NUMERIC_8:
{
current_mod_file_selecting -= 1;

// From the hxcmod documentation (note that stereo 16bits is not entirely true, we override
// it to be 8-bit mono for now)
if (current_mod_file_selecting < 0)
{
current_mod_file_selecting = 0;
}

// void hxcmod_fillbuffer( modcontext * modctx, unsigned short * outbuffer, unsigned long nbsample, tracker_buffer_state * trkbuf )
break;
}

// - Generate and return the next samples chunk to outbuffer.
// nbsample specify the number of stereo 16bits samples you want.
// The output format is signed 44100Hz 16-bit Stereo PCM samples.
// The output buffer size in byte must be equal to ( nbsample * 2 * 2 ).
// The optional trkbuf parameter can be used to get detailed status of the player. Put NULL/0 is unused.
case IPC_KEYBOARD_SPECIAL_KEY_NUMERIC_2:
{
current_mod_file_selecting += 1;

hxcmod_fillbuffer(&modctx, sound_message->data, BUFFER_SIZE * NUM_CHANNELS * BYTES_PER_SAMPLE, NULL);
if (current_mod_file_selecting >= NUM_MODFILES)
{
current_mod_file_selecting = NUM_MODFILES - 1;
}

if (sound_play_stream(&ipc_structure, sound_message) !=
SOUND_RETURN_SUCCESS)
break;
}

case IPC_KEYBOARD_SPECIAL_KEY_ENTER:
{
current_mod_file = current_mod_file_selecting;

if (!select_modfile(current_mod_file))
{
return -1;
}

break;
}
}
}
}

// Print out information about available modules. We do this
// after keypresses have been handled to provide a smoother UX.
for (int i = 0; i < NUM_MODFILES; i++)
{
log_print(&log_structure, LOG_URGENCY_EMERGENCY,
"Could not play the sample as wanted.");
return -1;
int color = i == current_mod_file ?
CONSOLE_BUFFER_COLOUR_BRIGHT_WHITE | CONSOLE_BUFFER_BG_COLOUR_BLUE :
CONSOLE_BUFFER_COLOUR_LIGHT_GRAY;

if (i == current_mod_file_selecting &&
i != current_mod_file)
{
color = CONSOLE_BUFFER_COLOUR_BRIGHT_WHITE;
}

// Abuse the fact that the .mod file begins with the name, hopefully NUL-terminated. :)
console_buffer_print_formatted(&console_structure, 0, 37 + i,
color, " [%d] %-20s", i + 1, modfiles[i]);
}

console_flip(&console_structure);
}

return 0;
}

static bool select_modfile(int current_mod_file)
{
uint8_t *modfile = modfiles[current_mod_file];
int modfile_size = modfile_sizes[current_mod_file];

if (!initialize_mod_player(modfile, modfile_size))
{
return FALSE;
}

print_mod_name();

return TRUE;
}

static bool initialize_mod_player(uint8_t *modfile, int length)
{
if (hxcmod_init(&modctx) == 0)
Expand All @@ -185,8 +370,32 @@ static bool initialize_mod_player(uint8_t *modfile, int length)
return TRUE;
}

static void initialize_tracker_buffer_state(tracker_buffer_state *trackbuf_state)
{
memory_set_uint8_t((uint8_t *) trackbuf_state, 0, sizeof(tracker_buffer_state));
trackbuf_state->nb_max_of_state = 1;
memory_allocate((void **) &trackbuf_state->track_state_buf, sizeof(tracker_state) * trackbuf_state->nb_max_of_state);
memory_set_uint8_t((uint8_t *) trackbuf_state->track_state_buf, 0,
sizeof(tracker_state) * trackbuf_state->nb_max_of_state);
trackbuf_state->sample_step = NUMBER_OF_SAMPLES / trackbuf_state->nb_max_of_state;
}

static void print_mod_name(void)
{
console_cursor_move(&console_structure, 0, 3);
console_print_formatted(&console_structure, "Playing %-20s\n", modctx.song.title);
console_buffer_print_formatted(&console_structure, 0, 2, DEFAULT_COLOR,
"Playing file: %-20s", modctx.song.title);
}

static uint16_t abs(int16_t value)
{
if (value < 0)
{
// Added case to attempt to handle -32768 => 32768 conversion properly (which is unable
// to store in a signed int16_t)
return 0 - (int32_t)value;
}
else
{
return value;
}
}
26 changes: 23 additions & 3 deletions programs/modplay/modplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,29 @@

#pragma once

#define BUFFER_SIZE 2000
#define NUM_CHANNELS 1 // 1 = mono, 2 = stereo
#define BYTES_PER_SAMPLE 1 // 1 = 8-bit, 2 = 16-bit
// The number of samples per buffer.
#define NUMBER_OF_SAMPLES 2000
#define NUM_CHANNELS 1 // 1 = mono, 2 = stereo
#define BYTES_PER_SAMPLE 1 // 1 = 8-bit, 2 = 16-bit
#define BUFFER_SIZE (NUMBER_OF_SAMPLES * NUM_CHANNELS * BYTES_PER_SAMPLE)

// Note: hxcmod_setcfg must be called if you wish to use something different than this.
#define FREQUENCY 44100

#define NUM_INSTRUMENTS 31

// Gets the smallest one of two values
#define MIN_OF_TWO(a, b) ((a) < (b) ? (a) : (b))

// Gets the largest one of two values
#define MAX_OF_TWO(a, b) ((a) > (b) ? (a) : (b))

//
// Colors
//

// Gray on black
#define DEFAULT_COLOR CONSOLE_BUFFER_COLOUR_LIGHT_GRAY

#define DEFAULT_INSTRUMENT_COLOR CONSOLE_BUFFER_COLOUR_LIGHT_GRAY
#define BRIGHT_INSTRUMENT_COLOR CONSOLE_BUFFER_COLOUR_BRIGHT_WHITE
1 change: 1 addition & 0 deletions programs/startup
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
//ramdisk/programs/cluido
//ramdisk/programs/modplay
Loading