Skip to content

Commit

Permalink
[programs/modplay] Add .mod visualization (#159)
Browse files Browse the repository at this point in the history
This was a bit tricky, but once I figured out that just rendering the
PCM value (shortening it to the appropriate number of bits) would look
reasonable, it wasn't so hard. The tricky part this time was finding a
good audio visualization algorithm.

Also added a listing of the modfiles available, and made it possible to
pick a file by using the arrow keys. This is interestingly enough a bit
laggy, I did some attempts to reduce the lag but... didn't really
succeed. I _did_ suceed in getting the audio playback to stutter,
though, and I really don't want that so... this will have to be good
enough for now.
  • Loading branch information
perlun authored Jul 21, 2019
1 parent c4bb50f commit 34c15d4
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 42 deletions.
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

0 comments on commit 34c15d4

Please sign in to comment.