Fix audio load fclose crashes

master
Allen Webster 2021-01-17 12:56:49 -08:00
parent 3ff0968538
commit ee3ad5c0e0
5 changed files with 376 additions and 365 deletions

View File

@ -12,22 +12,22 @@
function u32
AtomicAddU32AndReturnOriginal(u32 volatile *Value, u32 Addend)
{
// NOTE(casey): Returns the original value _prior_ to adding
u32 Result = _InterlockedExchangeAdd((long volatile*)Value, (long)Addend);
return(Result);
// NOTE(casey): Returns the original value _prior_ to adding
u32 Result = _InterlockedExchangeAdd((long volatile*)Value, (long)Addend);
return(Result);
}
function void
def_audio_begin_ticket_mutex(Audio_System *Crunky)
{
u32 Ticket = AtomicAddU32AndReturnOriginal(&Crunky->ticket, 1);
while(Ticket != Crunky->serving) {_mm_pause();}
u32 Ticket = AtomicAddU32AndReturnOriginal(&Crunky->ticket, 1);
while(Ticket != Crunky->serving) {_mm_pause();}
}
function void
def_audio_end_ticket_mutex(Audio_System *Crunky)
{
AtomicAddU32AndReturnOriginal(&Crunky->serving, 1);
AtomicAddU32AndReturnOriginal(&Crunky->serving, 1);
}
@ -38,131 +38,131 @@ global Audio_System def_audio_system = {};
function void
def_audio_init(void){
block_zero_struct(&def_audio_system);
system_set_source_mixer(&def_audio_system, def_audio_mix_sources);
system_set_destination_mixer(def_audio_mix_destination);
block_zero_struct(&def_audio_system);
system_set_source_mixer(&def_audio_system, def_audio_mix_sources);
system_set_destination_mixer(def_audio_mix_destination);
}
function void
def_audio_play_clip(Audio_Clip clip, Audio_Control *control){
clip.control = control;
Audio_System *Crunky = &def_audio_system;
def_audio_begin_ticket_mutex(Crunky);
if (Crunky->pending_clip_count < ArrayCount(Crunky->pending_clips))
{
Crunky->pending_clips[Crunky->pending_clip_count++] = clip;
}
def_audio_end_ticket_mutex(Crunky);
clip.control = control;
Audio_System *Crunky = &def_audio_system;
def_audio_begin_ticket_mutex(Crunky);
if (Crunky->pending_clip_count < ArrayCount(Crunky->pending_clips))
{
Crunky->pending_clips[Crunky->pending_clip_count++] = clip;
}
def_audio_end_ticket_mutex(Crunky);
}
internal b32
def_audio_is_playing(Audio_Control *control){
Audio_System *Crunky = &def_audio_system;
b32 result = (Crunky->generation - control->generation < 2);
return(result);
Audio_System *Crunky = &def_audio_system;
b32 result = (Crunky->generation - control->generation < 2);
return(result);
}
internal void
def_audio_stop(Audio_Control *control){
Audio_System *Crunky = &def_audio_system;
def_audio_begin_ticket_mutex(Crunky);
Audio_Clip *clip = Crunky->playing_clips;
for(u32 i = 0;
i < ArrayCount(Crunky->playing_clips);
i += 1, clip += 1){
if (clip->control == control){
clip->at_sample_index = clip->sample_count;
clip->control = 0;
}
}
control->loop = false;
def_audio_end_ticket_mutex(Crunky);
Audio_System *Crunky = &def_audio_system;
def_audio_begin_ticket_mutex(Crunky);
Audio_Clip *clip = Crunky->playing_clips;
for(u32 i = 0;
i < ArrayCount(Crunky->playing_clips);
i += 1, clip += 1){
if (clip->control == control){
clip->at_sample_index = clip->sample_count;
clip->control = 0;
}
}
control->loop = false;
def_audio_end_ticket_mutex(Crunky);
}
function void
def_audio_mix_sources(void *ctx, f32 *mix_buffer, u32 sample_count){
Audio_System *Crunky = (Audio_System*)ctx;
def_audio_begin_ticket_mutex(Crunky);
// NOTE(casey): Move pending sounds into the playing list
{
Crunky->generation += 1;
u32 PendIndex = 0;
Audio_Clip *clip = Crunky->playing_clips;
for(u32 DestIndex = 0;
(DestIndex < ArrayCount(Crunky->playing_clips)) && (PendIndex < Crunky->pending_clip_count);
DestIndex += 1, clip += 1)
{
if (clip->at_sample_index == clip->sample_count)
{
Audio_Control *control = clip->control;
if (control == 0 || !control->loop){
*clip = Crunky->pending_clips[PendIndex++];
}
}
}
Crunky->pending_clip_count = 0;
Audio_System *Crunky = (Audio_System*)ctx;
def_audio_begin_ticket_mutex(Crunky);
// NOTE(casey): Move pending sounds into the playing list
{
Crunky->generation += 1;
u32 PendIndex = 0;
Audio_Clip *clip = Crunky->playing_clips;
for(u32 DestIndex = 0;
(DestIndex < ArrayCount(Crunky->playing_clips)) && (PendIndex < Crunky->pending_clip_count);
DestIndex += 1, clip += 1)
{
if (clip->at_sample_index == clip->sample_count)
{
Audio_Control *control = clip->control;
if (control == 0 || !control->loop){
*clip = Crunky->pending_clips[PendIndex++];
}
def_audio_end_ticket_mutex(Crunky);
}
}
Crunky->pending_clip_count = 0;
}
def_audio_end_ticket_mutex(Crunky);
// NOTE(casey): Mix all sounds into the output buffer
{
Audio_Clip *clip = Crunky->playing_clips;
for(u32 SoundIndex = 0;
SoundIndex < ArrayCount(Crunky->playing_clips);
SoundIndex += 1, clip += 1)
{
// NOTE(allen): Determine starting point
Audio_Control *control = clip->control;
if (control != 0 && control->loop && clip->at_sample_index == clip->sample_count){
clip->at_sample_index = 0;
}
u32 base_sample_index = clip->at_sample_index;
// NOTE(casey): Determine how many samples are left to play in this
// sound (possible none)
u32 SamplesToMix = clamp_top((clip->sample_count - clip->at_sample_index), sample_count);
clip->at_sample_index += SamplesToMix;
// NOTE(casey): Load the volume out of the control if there is one,
// and if there is, update the generation and sample index so
// external controllers can take action
f32 LeftVol = clip->channel_volume[0];
f32 RightVol = clip->channel_volume[1];
if(SamplesToMix && control != 0)
{
LeftVol *= control->channel_volume[0];
RightVol *= control->channel_volume[1];
control->generation = Crunky->generation;
control->last_played_sample_index = clip->at_sample_index;
}
// NOTE(casey): Mix samples
for(u32 SampleIndex = 0;
SampleIndex < SamplesToMix;
++SampleIndex)
{
u32 src_index = 2*(base_sample_index + SampleIndex);
f32 Left = LeftVol *(f32)clip->samples[src_index + 0];
f32 Right = RightVol*(f32)clip->samples[src_index + 1];
// NOTE(casey): Mix all sounds into the output buffer
{
Audio_Clip *clip = Crunky->playing_clips;
for(u32 SoundIndex = 0;
SoundIndex < ArrayCount(Crunky->playing_clips);
SoundIndex += 1, clip += 1)
{
// NOTE(allen): Determine starting point
Audio_Control *control = clip->control;
if (control != 0 && control->loop && clip->at_sample_index == clip->sample_count){
clip->at_sample_index = 0;
}
u32 base_sample_index = clip->at_sample_index;
// NOTE(casey): Determine how many samples are left to play in this
// sound (possible none)
u32 SamplesToMix = clamp_top((clip->sample_count - clip->at_sample_index), sample_count);
clip->at_sample_index += SamplesToMix;
// NOTE(casey): Load the volume out of the control if there is one,
// and if there is, update the generation and sample index so
// external controllers can take action
f32 LeftVol = clip->channel_volume[0];
f32 RightVol = clip->channel_volume[1];
if(SamplesToMix && control != 0)
{
LeftVol *= control->channel_volume[0];
RightVol *= control->channel_volume[1];
control->generation = Crunky->generation;
control->last_played_sample_index = clip->at_sample_index;
}
// NOTE(casey): Mix samples
for(u32 SampleIndex = 0;
SampleIndex < SamplesToMix;
++SampleIndex)
{
u32 src_index = 2*(base_sample_index + SampleIndex);
f32 Left = LeftVol *(f32)clip->samples[src_index + 0];
f32 Right = RightVol*(f32)clip->samples[src_index + 1];
u32 dst_index = 2*SampleIndex;
mix_buffer[dst_index + 0] += Left;
mix_buffer[dst_index + 1] += Right;
}
}
}
u32 dst_index = 2*SampleIndex;
mix_buffer[dst_index + 0] += Left;
mix_buffer[dst_index + 1] += Right;
}
}
}
}
function void
def_audio_mix_destination(i16 *dst, f32 *src, u32 sample_count){
u32 opl = sample_count*2;
for(u32 i = 0; i < opl; i += 1){
f32 sample = src[i];
f32 sat_sample = clamp(-32768.f, sample, 32767.f);
dst[i] = (i16)sat_sample;
}
u32 opl = sample_count*2;
for(u32 i = 0; i < opl; i += 1){
f32 sample = src[i];
f32 sat_sample = clamp(-32768.f, sample, 32767.f);
dst[i] = (i16)sat_sample;
}
}
@ -174,97 +174,100 @@ def_audio_mix_destination(i16 *dst, f32 *src, u32 sample_count){
#pragma pack(push, 1)
struct wave_fmt_data
{
u16 wFormatTag;
u16 wChannels;
u32 dwSamplesPerSec;
u32 dwAvgBytesPerSec;
u16 wBlockAlign;
u16 wBitsPerSample;
u16 wFormatTag;
u16 wChannels;
u32 dwSamplesPerSec;
u32 dwAvgBytesPerSec;
u16 wBlockAlign;
u16 wBitsPerSample;
};
struct riff_header
{
u32 ID;
u32 DataSize;
u32 ID;
u32 DataSize;
};
#pragma pack(pop)
#endif
function Audio_Clip
audio_clip_from_wav_data(String_Const_u8 data){
Audio_Clip Result = {};
if (data.size >= 4 && *(u32 *)data.str == *(u32 *)"RIFF"){
// NOTE(casey): This ROM is in WAV format
riff_header *RootHeader = (riff_header *)data.str;
wave_fmt_data *Format = 0;
u32 SampleDataSize = 0;
i16 *Samples = 0;
u32 At = sizeof(riff_header);
u32 LastAt = At + ((RootHeader->DataSize + 1) & ~1);
if ((*(u32 *)(data.str + At) == *(u32 *)"WAVE") &&
(LastAt <= data.size)){
At += sizeof(u32);
while (At < LastAt){
riff_header *Header = (riff_header *)(data.str + At);
u32 DataAt = At + sizeof(riff_header);
u32 EndAt = DataAt + ((Header->DataSize + 1) & ~1);
if(EndAt <= data.size)
{
void *Data = (data.str + DataAt);
if(Header->ID == *(u32 *)"fmt ")
{
Format = (wave_fmt_data *)Data;
}
else if(Header->ID == *(u32 *)"data")
{
SampleDataSize = Header->DataSize;
Samples = (i16 *)Data;
}
}
At = EndAt;
}
}
if (Format &&
Samples &&
(Format->wFormatTag == 1) &&
(Format->wChannels == 2) &&
(Format->wBitsPerSample == 16) &&
(Format->dwSamplesPerSec == 48000)){
for (u32 i = 0; i < 2; i += 1){
Result.channel_volume[i] = 1.f;
}
Result.sample_count = SampleDataSize / (Format->wChannels*Format->wBitsPerSample/8);
Result.samples = (i16 *)Samples;
}
else{
// TODO(casey): This is where you would output an error - to 4coder somehow?
}
Audio_Clip Result = {};
if (data.size >= 4 && *(u32 *)data.str == *(u32 *)"RIFF"){
// NOTE(casey): This ROM is in WAV format
riff_header *RootHeader = (riff_header *)data.str;
wave_fmt_data *Format = 0;
u32 SampleDataSize = 0;
i16 *Samples = 0;
u32 At = sizeof(riff_header);
u32 LastAt = At + ((RootHeader->DataSize + 1) & ~1);
if ((*(u32 *)(data.str + At) == *(u32 *)"WAVE") &&
(LastAt <= data.size)){
At += sizeof(u32);
while (At < LastAt){
riff_header *Header = (riff_header *)(data.str + At);
u32 DataAt = At + sizeof(riff_header);
u32 EndAt = DataAt + ((Header->DataSize + 1) & ~1);
if(EndAt <= data.size)
{
void *Data = (data.str + DataAt);
if(Header->ID == *(u32 *)"fmt ")
{
Format = (wave_fmt_data *)Data;
}
else if(Header->ID == *(u32 *)"data")
{
SampleDataSize = Header->DataSize;
Samples = (i16 *)Data;
}
}
else{
// TODO(casey): This is where you would output an error - to 4coder somehow?
}
return(Result);
At = EndAt;
}
}
if (Format &&
Samples &&
(Format->wFormatTag == 1) &&
(Format->wChannels == 2) &&
(Format->wBitsPerSample == 16) &&
(Format->dwSamplesPerSec == 48000)){
for (u32 i = 0; i < 2; i += 1){
Result.channel_volume[i] = 1.f;
}
Result.sample_count = SampleDataSize / (Format->wChannels*Format->wBitsPerSample/8);
Result.samples = (i16 *)Samples;
}
else{
// TODO(casey): This is where you would output an error - to 4coder somehow?
}
}
else{
// TODO(casey): This is where you would output an error - to 4coder somehow?
}
return(Result);
}
function Audio_Clip
audio_clip_from_wav_FILE(Arena *arena, FILE *file){
String_Const_u8 data = data_from_file(arena, file);
Audio_Clip result = audio_clip_from_wav_data(data);
return(result);
String_Const_u8 data = data_from_file(arena, file);
Audio_Clip result = audio_clip_from_wav_data(data);
return(result);
}
function Audio_Clip
audio_clip_from_wav_file_name(Arena *arena, char *file_name){
String_Const_u8 data = {};
FILE *file = fopen(file_name, "rb");
Audio_Clip result = audio_clip_from_wav_FILE(arena, file);
fclose(file);
return(result);
Audio_Clip result = {};
String_Const_u8 data = {};
FILE *file = fopen(file_name, "rb");
if (file != 0){
result = audio_clip_from_wav_FILE(arena, file);
fclose(file);
}
return(result);
}

View File

@ -10,18 +10,18 @@ customization writers.
CUSTOM_COMMAND_SIG(double_backspace)
CUSTOM_DOC("Example of history group helpers")
{
/* History_Group is a wrapper around the history API that makes it easy to
/* History_Group is a wrapper around the history API that makes it easy to
group any series of edits into a single undo/redo record in the buffer's history.
Before any edits call history_group_begin and afterwards call history_group_end.
After history_group_end all of the edits to the buffer supplied in history_group_begin
will be merged, including all edits from function and command calls. */
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
History_Group group = history_group_begin(app, buffer);
backspace_char(app);
backspace_char(app);
history_group_end(group);
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
History_Group group = history_group_begin(app, buffer);
backspace_char(app);
backspace_char(app);
history_group_end(group);
}
// tags: query; bar
@ -29,8 +29,8 @@ will be merged, including all edits from function and command calls. */
CUSTOM_COMMAND_SIG(play_with_a_counter)
CUSTOM_DOC("Example of query bar")
{
/* Query bars make a quick lightweight display of a single line of text for interactive
commands, while still showing the buffer. Query bars are convenient because they don't
/* Query bars make a quick lightweight display of a single line of text for interactive
commands, while still showing the buffer. Query bars are convenient because they don't
require any complex UI setup, or extra rendering work inside your command.
First, constructing a Query_Bar_Group is a convenient way to make sure the query bar
@ -40,49 +40,49 @@ Second, we make our query bar and start showing it with start_query_bar. Until w
call end_query_bar on the same bar, or the group's destructor runs, the bar struct
needs to remain in memory. The easy way to accomplish this is to just let the bar be
on the commands stack frame. */
i32 counter = 0;
Query_Bar_Group group(app);
Query_Bar dumb_bar = {};
dumb_bar.prompt = SCu8("Goes away at >= 10");
if (!start_query_bar(app, &dumb_bar, 0)){
return;
}
Query_Bar bar = {};
bar.prompt = SCu8("Counter = ");
bar.string = SCu8("");
if (!start_query_bar(app, &bar, 0)){
return;
}
for (;;){
/* Notice here, we set the string of the query bar BEFORE we call get_next_input.
get_next_input blocks this command until the next input is sent from the core. Whatever
i32 counter = 0;
Query_Bar_Group group(app);
Query_Bar dumb_bar = {};
dumb_bar.prompt = SCu8("Goes away at >= 10");
if (!start_query_bar(app, &dumb_bar, 0)){
return;
}
Query_Bar bar = {};
bar.prompt = SCu8("Counter = ");
bar.string = SCu8("");
if (!start_query_bar(app, &bar, 0)){
return;
}
for (;;){
/* Notice here, we set the string of the query bar BEFORE we call get_next_input.
get_next_input blocks this command until the next input is sent from the core. Whatever
string we put in the bar now will be shown and remain in the bar until an event wakes
up this command and we get a chance to modify the bar again. */
Scratch_Block scratch(app);
bar.string = push_stringf(scratch, "%d", counter);
if (counter >= 10){
end_query_bar(app, &dumb_bar, 0);
}
User_Input in = get_next_input(app, EventPropertyGroup_Any, EventProperty_Escape);
if (in.abort){
break;
}
if (match_key_code(&in.event, KeyCode_Up)){
counter += 1;
}
else if (match_key_code(&in.event, KeyCode_Down)){
counter -= 1;
}
else{
leave_current_input_unhandled(app);
}
}
Scratch_Block scratch(app);
bar.string = push_stringf(scratch, "%d", counter);
if (counter >= 10){
end_query_bar(app, &dumb_bar, 0);
}
User_Input in = get_next_input(app, EventPropertyGroup_Any, EventProperty_Escape);
if (in.abort){
break;
}
if (match_key_code(&in.event, KeyCode_Up)){
counter += 1;
}
else if (match_key_code(&in.event, KeyCode_Down)){
counter -= 1;
}
else{
leave_current_input_unhandled(app);
}
}
}
// tags: input; loop
@ -90,46 +90,46 @@ up this command and we get a chance to modify the bar again. */
CUSTOM_COMMAND_SIG(display_key_codes)
CUSTOM_DOC("Example of input handling loop")
{
/* In the 4coder custom layer, inputs are handled by a view context. A view context is a
/* In the 4coder custom layer, inputs are handled by a view context. A view context is a
thread that hands off control with the main thread of the 4coder core. When a command is
running in a view context thread, it can wait for inputs from the core by calling
get_next_input. If your command gets inputs from the core, then default input handling
isn't happening, so command bindings don't trigger unless you trigger them yourself. */
Query_Bar_Group group(app);
Query_Bar bar = {};
bar.prompt = SCu8("KeyCode = ");
if (!start_query_bar(app, &bar, 0)){
return;
}
Key_Code code = 0;
b32 is_dead_key = false;
for (;;){
Scratch_Block scratch(app);
if (code == 0){
bar.string = SCu8("...");
}
else{
bar.string = push_stringf(scratch, "KeyCode_%s (%d)%s", key_code_name[code], code,
is_dead_key?" dead-key":"");
}
User_Input in = get_next_input(app, EventPropertyGroup_Any, EventProperty_Escape);
if (in.abort){
break;
}
if (in.event.kind == InputEventKind_KeyStroke){
code = in.event.key.code;
is_dead_key = event_is_dead_key(&in.event);
}
else{
/* Marking inputs as handled lets the core determine if certain inputs should
be passed to additional handlers. This is especially important for text input,
which is explained more in the example display_text_input. */
leave_current_input_unhandled(app);
}
}
Query_Bar_Group group(app);
Query_Bar bar = {};
bar.prompt = SCu8("KeyCode = ");
if (!start_query_bar(app, &bar, 0)){
return;
}
Key_Code code = 0;
b32 is_dead_key = false;
for (;;){
Scratch_Block scratch(app);
if (code == 0){
bar.string = SCu8("...");
}
else{
bar.string = push_stringf(scratch, "KeyCode_%s (%d)%s", key_code_name[code], code,
is_dead_key?" dead-key":"");
}
User_Input in = get_next_input(app, EventPropertyGroup_Any, EventProperty_Escape);
if (in.abort){
break;
}
if (in.event.kind == InputEventKind_KeyStroke){
code = in.event.key.code;
is_dead_key = event_is_dead_key(&in.event);
}
else{
/* Marking inputs as handled lets the core determine if certain inputs should
be passed to additional handlers. This is especially important for text input,
which is explained more in the example display_text_input. */
leave_current_input_unhandled(app);
}
}
}
// tags: text; input
@ -137,41 +137,41 @@ isn't happening, so command bindings don't trigger unless you trigger them yours
CUSTOM_COMMAND_SIG(display_text_input)
CUSTOM_DOC("Example of to_writable and leave_current_input_unhandled")
{
/* In the 4coder custom layer, inputs are handled by a view context. A view context is a
/* In the 4coder custom layer, inputs are handled by a view context. A view context is a
thread that hands off control with the main thread of the 4coder core. When a command is
running in a view context thread, it can wait for inputs from the core by calling
get_next_input. If your command gets inputs from the core, then default input handling
isn't happening, so command bindings don't trigger unless you trigger them yourself. */
Query_Bar_Group group(app);
Query_Bar bar = {};
bar.prompt = SCu8("Weird String: ");
if (!start_query_bar(app, &bar, 0)){
return;
}
u8 buffer[256];
u64 size = 0;
for (;;){
User_Input in = get_next_input(app, EventPropertyGroup_Any, EventProperty_Escape);
if (in.abort){
break;
}
String_Const_u8 in_string = to_writable(&in);
if (in_string.size > 0){
size = clamp_top(in_string.size, sizeof(buffer));
block_copy(buffer, in_string.str, size);
bar.string = SCu8(buffer, size);
}
else if (in.event.kind == InputEventKind_KeyStroke){
/* If we handle a key stroke then the core marks any text input generated from that
Query_Bar_Group group(app);
Query_Bar bar = {};
bar.prompt = SCu8("Weird String: ");
if (!start_query_bar(app, &bar, 0)){
return;
}
u8 buffer[256];
u64 size = 0;
for (;;){
User_Input in = get_next_input(app, EventPropertyGroup_Any, EventProperty_Escape);
if (in.abort){
break;
}
String_Const_u8 in_string = to_writable(&in);
if (in_string.size > 0){
size = clamp_top(in_string.size, sizeof(buffer));
block_copy(buffer, in_string.str, size);
bar.string = SCu8(buffer, size);
}
else if (in.event.kind == InputEventKind_KeyStroke){
/* If we handle a key stroke then the core marks any text input generated from that
key stroke as handled too, and the text input is never passed. By marking key strokes
as unhandled, we ensure we get text input events. */
leave_current_input_unhandled(app);
}
}
leave_current_input_unhandled(app);
}
}
}
// tags: string; number; query; user
@ -179,33 +179,33 @@ as unhandled, we ensure we get text input events. */
CUSTOM_COMMAND_SIG(string_repeat)
CUSTOM_DOC("Example of query_user_string and query_user_number")
{
Query_Bar_Group group(app);
Query_Bar string_bar = {};
string_bar.prompt = SCu8("String: ");
u8 string_buffer[KB(1)];
string_bar.string.str = string_buffer;
string_bar.string_capacity = sizeof(string_buffer);
Query_Bar number_bar = {};
number_bar.prompt = SCu8("Repeat Count: ");
u8 number_buffer[KB(1)];
number_bar.string.str = number_buffer;
number_bar.string_capacity = sizeof(number_buffer);
if (query_user_string(app, &string_bar)){
if (string_bar.string.size > 0){
if (query_user_number(app, &number_bar)){
if (number_bar.string.size > 0){
i32 repeats = (i32)string_to_integer(number_bar.string, 10);
repeats = clamp_top(repeats, 1000);
Scratch_Block scratch(app);
String_Const_u8 msg = push_stringf(scratch, "%.*s\n", string_expand(string_bar.string));
for (i32 i = 0; i < repeats; i += 1){
print_message(app, msg);
}
}
}
}
Query_Bar_Group group(app);
Query_Bar string_bar = {};
string_bar.prompt = SCu8("String: ");
u8 string_buffer[KB(1)];
string_bar.string.str = string_buffer;
string_bar.string_capacity = sizeof(string_buffer);
Query_Bar number_bar = {};
number_bar.prompt = SCu8("Repeat Count: ");
u8 number_buffer[KB(1)];
number_bar.string.str = number_buffer;
number_bar.string_capacity = sizeof(number_buffer);
if (query_user_string(app, &string_bar)){
if (string_bar.string.size > 0){
if (query_user_number(app, &number_bar)){
if (number_bar.string.size > 0){
i32 repeats = (i32)string_to_integer(number_bar.string, 10);
repeats = clamp_top(repeats, 1000);
Scratch_Block scratch(app);
String_Const_u8 msg = push_stringf(scratch, "%.*s\n", string_expand(string_bar.string));
for (i32 i = 0; i < repeats; i += 1){
print_message(app, msg);
}
}
}
}
}
}
global Audio_Control the_music_control = {};
@ -213,50 +213,54 @@ global Audio_Control the_music_control = {};
CUSTOM_COMMAND_SIG(music_start)
CUSTOM_DOC("Starts the music.")
{
local_persist Audio_Clip the_music_clip = {};
if (the_music_clip.sample_count == 0){
Scratch_Block scratch(app);
FILE *file = def_search_normal_fopen(scratch, "audio_test/chtulthu.wav", "rb");
the_music_clip = audio_clip_from_wav_FILE(&global_permanent_arena, file);
fclose(file);
}
if (!def_audio_is_playing(&the_music_control)){
the_music_control.loop = true;
the_music_control.channel_volume[0] = 1.f;
the_music_control.channel_volume[1] = 1.f;
def_audio_play_clip(the_music_clip, &the_music_control);
}
local_persist Audio_Clip the_music_clip = {};
if (the_music_clip.sample_count == 0){
Scratch_Block scratch(app);
FILE *file = def_search_normal_fopen(scratch, "audio_test/chtulthu.wav", "rb");
if (file != 0){
the_music_clip = audio_clip_from_wav_FILE(&global_permanent_arena, file);
fclose(file);
}
}
if (!def_audio_is_playing(&the_music_control)){
the_music_control.loop = true;
the_music_control.channel_volume[0] = 1.f;
the_music_control.channel_volume[1] = 1.f;
def_audio_play_clip(the_music_clip, &the_music_control);
}
}
CUSTOM_COMMAND_SIG(music_stop)
CUSTOM_DOC("Stops the music.")
{
def_audio_stop(&the_music_control);
def_audio_stop(&the_music_control);
}
CUSTOM_COMMAND_SIG(hit_sfx)
CUSTOM_DOC("Play the hit sound effect")
{
local_persist Audio_Clip the_hit_clip = {};
if (the_hit_clip.sample_count == 0){
Scratch_Block scratch(app);
FILE *file = def_search_normal_fopen(scratch, "audio_test/hit.wav", "rb");
the_hit_clip = audio_clip_from_wav_FILE(&global_permanent_arena, file);
fclose(file);
}
local_persist u32 index = 0;
local_persist Audio_Control controls[8] = {};
Audio_Control *control = &controls[index%8];
if (!def_audio_is_playing(control)){
control->loop = false;
control->channel_volume[0] = 1.f;
control->channel_volume[1] = 1.f;
def_audio_play_clip(the_hit_clip, control);
index += 1;
}
local_persist Audio_Clip the_hit_clip = {};
if (the_hit_clip.sample_count == 0){
Scratch_Block scratch(app);
FILE *file = def_search_normal_fopen(scratch, "audio_test/hit.wav", "rb");
if (file != 0){
the_hit_clip = audio_clip_from_wav_FILE(&global_permanent_arena, file);
fclose(file);
}
}
local_persist u32 index = 0;
local_persist Audio_Control controls[8] = {};
Audio_Control *control = &controls[index%8];
if (!def_audio_is_playing(control)){
control->loop = false;
control->channel_volume[0] = 1.f;
control->channel_volume[1] = 1.f;
def_audio_play_clip(the_hit_clip, control);
index += 1;
}
}

View File

@ -340,7 +340,7 @@ static Command_Metadata fcoder_metacmd_table[252] = {
{ PROC_LINKS(goto_prev_jump_no_skips, 0), false, "goto_prev_jump_no_skips", 23, "If a buffer containing jump locations has been locked in, goes to the previous jump in the buffer, and does not skip sub jump locations.", 136, "../code/custom/4coder_jump_sticky.cpp", 37, 511 },
{ PROC_LINKS(hide_filebar, 0), false, "hide_filebar", 12, "Sets the current view to hide it's filebar.", 43, "../code/custom/4coder_base_commands.cpp", 39, 704 },
{ PROC_LINKS(hide_scrollbar, 0), false, "hide_scrollbar", 14, "Sets the current view to hide it's scrollbar.", 45, "../code/custom/4coder_base_commands.cpp", 39, 690 },
{ PROC_LINKS(hit_sfx, 0), false, "hit_sfx", 7, "Play the hit sound effect", 25, "../code/custom/4coder_examples.cpp", 34, 238 },
{ PROC_LINKS(hit_sfx, 0), false, "hit_sfx", 7, "Play the hit sound effect", 25, "../code/custom/4coder_examples.cpp", 34, 240 },
{ PROC_LINKS(hms_demo_tutorial, 0), false, "hms_demo_tutorial", 17, "Tutorial for built in 4coder bindings and features.", 51, "../code/custom/4coder_tutorial.cpp", 34, 869 },
{ PROC_LINKS(if0_off, 0), false, "if0_off", 7, "Surround the range between the cursor and mark with an '#if 0' and an '#endif'", 78, "../code/custom/4coder_combined_write_commands.cpp", 49, 70 },
{ PROC_LINKS(if_read_only_goto_position, 0), false, "if_read_only_goto_position", 26, "If the buffer in the active view is writable, inserts a character, otherwise performs goto_jump_at_cursor.", 106, "../code/custom/4coder_jump_sticky.cpp", 37, 564 },
@ -416,7 +416,7 @@ static Command_Metadata fcoder_metacmd_table[252] = {
{ PROC_LINKS(multi_paste_interactive, 0), false, "multi_paste_interactive", 23, "Paste multiple lines from the clipboard history, controlled with arrow keys", 75, "../code/custom/4coder_clipboard.cpp", 35, 371 },
{ PROC_LINKS(multi_paste_interactive_quick, 0), false, "multi_paste_interactive_quick", 29, "Paste multiple lines from the clipboard history, controlled by inputing the number of lines to paste", 100, "../code/custom/4coder_clipboard.cpp", 35, 380 },
{ PROC_LINKS(music_start, 0), false, "music_start", 11, "Starts the music.", 17, "../code/custom/4coder_examples.cpp", 34, 213 },
{ PROC_LINKS(music_stop, 0), false, "music_stop", 10, "Stops the music.", 16, "../code/custom/4coder_examples.cpp", 34, 232 },
{ PROC_LINKS(music_stop, 0), false, "music_stop", 10, "Stops the music.", 16, "../code/custom/4coder_examples.cpp", 34, 234 },
{ PROC_LINKS(open_all_code, 0), false, "open_all_code", 13, "Open all code in the current directory. File types are determined by extensions. An extension is considered code based on the extensions specified in 4coder.config.", 164, "../code/custom/4coder_project_commands.cpp", 42, 805 },
{ PROC_LINKS(open_all_code_recursive, 0), false, "open_all_code_recursive", 23, "Works as open_all_code but also runs in all subdirectories.", 59, "../code/custom/4coder_project_commands.cpp", 42, 814 },
{ PROC_LINKS(open_file_in_quotes, 0), false, "open_file_in_quotes", 19, "Reads a filename from surrounding '\"' characters and attempts to open the corresponding file.", 94, "../code/custom/4coder_base_commands.cpp", 39, 1576 },

BIN
custom/metadata_generator Executable file

Binary file not shown.

View File

@ -26,35 +26,39 @@ load_paths = {
commands = {
.build_x64 = {
.win = "echo build: x64 & bin\build.bat",
.win = "echo build: x64 & bin\\build.bat",
.linux = "echo build: x64 & bin/build-linux.sh",
.out = "*compilation*",
.footer_panel = true,
.save_dirty_files = true,
.cursor_at_end = false,
},
.build_x86 = {
.win = "echo build: x86 & bin\build.bat /DDEV_BUILD_X86",
.win = "echo build: x86 & bin\\build.bat /DDEV_BUILD_X86",
.linux = "echo build: x86 & bin/build-linux.sh /DDEV_BUILD_X86",
.out = "*compilation*",
.footer_panel = true,
.save_dirty_files = true,
.cursor_at_end = false,
},
.package = {
.win = "echo package & bin\package.bat",
.win = "echo package & bin\\package.bat",
.linux = "echo package & bin/package.sh",
.out = "*compilation*",
.footer_panel = false,
.save_dirty_files = true,
.cursor_at_end = false,
},
.run_one_time = {
.win = "pushd ..\build & one_time",
.win = "pushd ..\\build & one_time",
.linux = "pushd ../build & one_time",
.out = "*run*",
.footer_panel = false,
.save_dirty_files = false,
.cursor_at_end = false,
},
.build_custom_api_docs = {
.win = "custom\bin\build_one_time docs\4ed_doc_custom_api_main.cpp ..\build",
.win = "custom\\bin\\build_one_time docs\\4ed_doc_custom_api_main.cpp ..\\build",
.out = "*compilation*",
.footer_panel = true,
.save_dirty_files = true,