int save_wav(const char * fn, SAMPLE * sp) { PACKFILE * file; eof_log("save_wav() entered", 1); if(!fn || !sp) { return 0; } /* open file */ file = pack_fopen(fn, "w"); if(file == NULL) { (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tError saving WAV: \"%s\"", strerror(errno)); //Get the Operating System's reason for the failure eof_log(eof_log_string, 1); return 0; } /* save WAV to the file */ if(!save_wav_fp(sp, file)) { (void) pack_fclose(file); return 0; } /* close the file */ (void) pack_fclose(file); return 1; }
void eof_move_text_events(EOF_SONG * sp, unsigned long beat, unsigned long offset, int dir) { unsigned long i; eof_log("eof_move_text_events() entered", 1); if(!sp) { return; } for(i = 0; i < sp->text_events; i++) { if(sp->text_event[i]->beat >= beat) { if(sp->text_event[i]->beat >= sp->beats) continue; //Do not allow an out of bound access sp->beat[sp->text_event[i]->beat]->flags &= ~(EOF_BEAT_FLAG_EVENTS); //Clear the event flag if(dir < 0) { if(offset > sp->text_event[i]->beat) continue; //Do not allow an underflow sp->text_event[i]->beat -= offset; } else { if(sp->text_event[i]->beat + offset >= sp->beats) continue; //Do not allow an overflow sp->text_event[i]->beat += offset; } sp->beat[sp->text_event[i]->beat]->flags |= EOF_BEAT_FLAG_EVENTS; //Set the event flag } } }
void eof_controller_read_button_names(EOF_CONTROLLER * cp) { int i; eof_log("eof_controller_read_button_names() entered", 1); if(!cp) { return; } for(i = 0; i < EOF_CONTROLLER_MAX_BUTTONS; i++) { switch(cp->button[i].type) { case EOF_CONTROLLER_BUTTON_TYPE_KEY: { (void) ustrcpy(cp->button[i].name, scancode_to_name(cp->button[i].key)); break; } case EOF_CONTROLLER_BUTTON_TYPE_JOYBUTTON: { (void) snprintf(cp->button[i].name, sizeof(cp->button[i].name) - 1, "Joy %d %s", cp->button[i].joy, joy[cp->button[i].joy].button[cp->button[i].key].name); break; } case EOF_CONTROLLER_BUTTON_TYPE_JOYAXIS: { (void) snprintf(cp->button[i].name, sizeof(cp->button[i].name) - 1, "Joy %d %s Axis (%s)", cp->button[i].joy, joy[cp->button[i].joy].stick[cp->button[i].index].axis[cp->button[i].key].name, cp->button[i].d == 0 ? "-" : "+"); break; } } } }
void eof_undo_reset(void) { eof_log("eof_undo_reset() entered", 1); eof_undo_current_index = 0; eof_undo_count = 0; eof_redo_count = 0; }
void eof_mix_seek(int pos) { int i; eof_log("eof_mix_seek() entered", 2); eof_mix_next_clap = -1; eof_mix_next_metronome = -1; eof_mix_next_note = -1; eof_mix_next_percussion = -1; eof_mix_sample_count = eof_mix_msec_to_sample(pos, alogg_get_wave_freq_ogg(eof_music_track)); for(i = 0; i < eof_mix_claps; i++) { if(eof_mix_clap_pos[i] >= eof_mix_sample_count) { eof_mix_current_clap = i; eof_mix_next_clap = eof_mix_clap_pos[i]; break; } } for(i = 0; i < eof_mix_metronomes; i++) { if(eof_mix_metronome_pos[i] >= eof_mix_sample_count) { eof_mix_current_metronome = i; eof_mix_next_metronome = eof_mix_metronome_pos[i]; break; } } for(i = 0; i < eof_mix_notes; i++) { if(eof_mix_note_pos[i] >= eof_mix_sample_count) { eof_mix_current_note = i; eof_mix_next_note = eof_mix_note_pos[i]; break; } } for(i = 0; i < eof_mix_percussions; i++) { if(eof_mix_percussion_pos[i] >= eof_mix_sample_count) { eof_mix_current_percussion = i; eof_mix_next_percussion = eof_mix_percussion_pos[i]; break; } } for(i = 0; i < eof_mix_guitar_notes; i++) { if(eof_guitar_notes[i].pos >= eof_mix_sample_count) { eof_mix_current_guitar_note = i; eof_mix_next_guitar_note = eof_guitar_notes[i].pos; break; } } }
void eof_window_destroy(EOF_WINDOW * wp) { eof_log("eof_window_destroy() entered", 1); if(wp) { destroy_bitmap(wp->screen); free(wp); } }
static unsigned long msec_to_samples(unsigned long msec) { unsigned long sample; double second = (double)msec / (double)1000.0; unsigned long freq = alogg_get_wave_freq_ogg(eof_music_track); eof_log("msec_to_samples() entered", 1); sample = (unsigned long)(second * (double)freq); return sample; }
int eof_copy_file(const char * src, const char * dest) { PACKFILE * src_fp = NULL; PACKFILE * dest_fp = NULL; void *ptr = NULL; //Used to buffer memory unsigned long src_size = 0; unsigned long i; eof_log("eof_copy_file() entered", 1); if((src == NULL) || (dest == NULL)) return 0; if(!ustricmp(src,dest)) //Special case: The input and output file are the same return 0; //Return success without copying any files src_size = (unsigned long)file_size_ex(src); if(src_size > LONG_MAX) return 0; //Unable to validate I/O due to Allegro's usage of signed values src_fp = pack_fopen(src, "r"); if(!src_fp) { return 0; } dest_fp = pack_fopen(dest, "w"); if(!dest_fp) { (void) pack_fclose(src_fp); return 0; } //Attempt to buffer the input file into memory for faster read and write ptr = malloc((size_t)src_size); if(ptr != NULL) { //If a buffer large enough to store the input file was created long long_src_size = src_size; if((pack_fread(ptr, long_src_size, src_fp) != long_src_size) || (pack_fwrite(ptr, long_src_size, dest_fp) != long_src_size)) { //If there was an error reading from file or writing from memory free(ptr); //Release buffer return 0; //Return error } free(ptr); //Release buffer } else { //Otherwise copy the slow way (one byte at a time) for(i = 0; i < src_size; i++) { (void) pack_putc(pack_getc(src_fp), dest_fp); } } (void) pack_fclose(src_fp); (void) pack_fclose(dest_fp); return 1; }
int eof_file_compare(char *file1, char *file2) { uint64_t filesize,ctr; int data1,data2; PACKFILE *fp1 = NULL,*fp2 = NULL; char result = 0; //The result is assumed to "files identical" until found otherwise eof_log("eof_file_compare() entered", 1); if((file1 == NULL) || (file2 == NULL)) { return 2; //Return error } filesize = file_size_ex(file1); //Get length of file1 if(filesize != file_size_ex(file2)) { //If file1 and file2 are different lengths return 1; //Return files don't match } fp1 = pack_fopen(file1, "r"); if(fp1 == NULL) { return 2; //Return error } fp2 = pack_fopen(file2, "r"); if(fp2 == NULL) { (void) pack_fclose(fp1); return 2; //Return error } for(ctr = 0;ctr < filesize; ctr++) { //For each byte in the files data1 = pack_getc(fp1); //Read one byte from each data2 = pack_getc(fp2); if((data1 == EOF) || (data2 == EOF)) { //If EOF was reached unexpectedly break; //Exit loop } if(data1 != data2) { result = 1; //Store a "non identical" result break; //Exit loop } } (void) pack_fclose(fp1); (void) pack_fclose(fp2); return result; }
int eof_undo_load_state(const char * fn) { EOF_SONG * sp = NULL; PACKFILE * fp = NULL, * rfp = NULL; char rheader[16] = {0}; int old_eof_silence_loaded = eof_silence_loaded; //Retain this value, since it is destroyed by eof_destroy_song() char eof_recover_path[50]; eof_log("eof_undo_load_state() entered", 1); if(fn == NULL) { return 0; } fp = pack_fopen(fn, "r"); if(!fp) { return 0; } if(pack_fread(rheader, 16, fp) != 16) { return 0; //Return error if 16 bytes cannot be read } sp = eof_create_song(); //Initialize an empty chart if(sp == NULL) { return 0; } sp->tags->accurate_ts = 0; //For existing projects, this setting must be manually enabled in order to prevent unwanted alteration to beat timings (void) snprintf(eof_recover_path, sizeof(eof_recover_path) - 1, "%seof.recover", eof_temp_path); rfp = pack_fopen(eof_recover_path, "r"); //Open the recovery file to prevent eof_destroy_song() from deleting it if(!eof_load_song_pf(sp, fp)) { //If loading the undo state fails allegro_message("Failed to perform undo"); (void) pack_fclose(rfp); //Close this recovery file handle so that it will be deleted by the following call to eof_destroy_song() eof_destroy_song(sp); return 0; //Return failure } if(eof_song) { eof_destroy_song(eof_song); //Destroy the chart that is open } (void) pack_fclose(rfp); eof_song = sp; //Replacing it with the loaded undo state eof_silence_loaded = old_eof_silence_loaded; //Restore the status of whether chart audio is loaded (void) pack_fclose(fp); return 1; }
void eof_mix_init(void) { int i; char fbuffer[1024] = {0}; eof_log("eof_mix_init() entered", 1); eof_sound_clap = load_wav("eof.dat#clap.wav"); if(!eof_sound_clap) { allegro_message("Couldn't load clap sound!"); } eof_sound_metronome = load_wav("eof.dat#metronome.wav"); if(!eof_sound_metronome) { allegro_message("Couldn't load metronome sound!"); } eof_sound_grid_snap = load_wav("eof.dat#gridsnap.wav"); if(!eof_sound_grid_snap) { allegro_message("Couldn't load seek sound!"); } for(i = 0; i < EOF_MAX_VOCAL_TONES; i++) { //Load piano tones (void) snprintf(fbuffer, sizeof(fbuffer) - 1, "eof.dat#piano.esp/NOTE_%02d_OGG", i); eof_sound_note[i] = eof_mix_load_ogg_sample(fbuffer); } eof_sound_cowbell = eof_mix_load_ogg_sample("percussion.dat#cowbell.ogg"); eof_sound_chosen_percussion = eof_sound_cowbell; //Until the user specifies otherwise, make cowbell the default percussion eof_sound_tambourine1 = eof_mix_load_ogg_sample("percussion.dat#tambourine1.ogg"); eof_sound_tambourine2 = eof_mix_load_ogg_sample("percussion.dat#tambourine2.ogg"); eof_sound_tambourine3 = eof_mix_load_ogg_sample("percussion.dat#tambourine3.ogg"); eof_sound_triangle1 = eof_mix_load_ogg_sample("percussion.dat#triangle1.ogg"); eof_sound_triangle2 = eof_mix_load_ogg_sample("percussion.dat#triangle2.ogg"); eof_sound_woodblock1 = eof_mix_load_ogg_sample("percussion.dat#woodblock1.ogg"); eof_sound_woodblock2 = eof_mix_load_ogg_sample("percussion.dat#woodblock2.ogg"); eof_sound_woodblock3 = eof_mix_load_ogg_sample("percussion.dat#woodblock3.ogg"); eof_sound_woodblock4 = eof_mix_load_ogg_sample("percussion.dat#woodblock4.ogg"); eof_sound_woodblock5 = eof_mix_load_ogg_sample("percussion.dat#woodblock5.ogg"); eof_sound_woodblock6 = eof_mix_load_ogg_sample("percussion.dat#woodblock6.ogg"); eof_sound_woodblock7 = eof_mix_load_ogg_sample("percussion.dat#woodblock7.ogg"); eof_sound_woodblock8 = eof_mix_load_ogg_sample("percussion.dat#woodblock8.ogg"); eof_sound_woodblock9 = eof_mix_load_ogg_sample("percussion.dat#woodblock9.ogg"); eof_sound_woodblock10 = eof_mix_load_ogg_sample("percussion.dat#woodblock10.ogg"); eof_sound_clap1 = eof_mix_load_ogg_sample("percussion.dat#clap1.ogg"); eof_sound_clap2 = eof_mix_load_ogg_sample("percussion.dat#clap2.ogg"); eof_sound_clap3 = eof_mix_load_ogg_sample("percussion.dat#clap3.ogg"); eof_sound_clap4 = eof_mix_load_ogg_sample("percussion.dat#clap4.ogg"); }
SAMPLE * create_silence_sample(unsigned long ms) { SAMPLE * sp = NULL; int bits; int stereo; int freq; unsigned long samples, i; unsigned channels; eof_log("create_silence_sample() entered", 1); if(eof_music_track) { bits = alogg_get_wave_bits_ogg(eof_music_track); stereo = alogg_get_wave_is_stereo_ogg(eof_music_track); freq = alogg_get_wave_freq_ogg(eof_music_track); samples = msec_to_samples(ms); channels = stereo ? 2 : 1; } else { bits = 16; stereo = 1; freq = 44100; samples = (double)(ms * freq) / 1000.0; channels = 2; } sp = create_sample(bits, stereo, freq, samples); if(!sp) return NULL; //Return error if(bits == 8) { for(i = 0; i < samples * channels; i++) { ((unsigned char *)(sp->data))[i] = 0x80; } } else { for(i = 0; i < samples * channels; i++) { ((unsigned short *)(sp->data))[i] = 0x8000; } } return sp; }
void eof_sort_events(EOF_SONG * sp) { unsigned long ctr; eof_log("eof_sort_events() entered", 1); if(sp) { for(ctr = 0; ctr < sp->text_events; ctr++) { //For each text event in the project sp->text_event[ctr]->index = ctr; //Store the native index into the event to prevent qsort() from corrupting the order of events that otherwise have matching sort criteria } qsort(sp->text_event, (size_t)sp->text_events, sizeof(EOF_TEXT_EVENT *), eof_song_qsort_events); } }
void eof_destroy_waveform(struct wavestruct *ptr) { eof_log("eof_destroy_waveform() entered", 1); if(ptr) { if(ptr->oggfilename) free(ptr->oggfilename); if(ptr->left.slices) free(ptr->left.slices); if(ptr->right.slices) free(ptr->right.slices); free(ptr); } }
void eof_controller_load_config(EOF_CONTROLLER * cp, char * name) { int i; char string[256] = {0}; eof_log("eof_controller_load_config() entered", 1); if(!cp || !name) { return; } for(i = 0; i < EOF_CONTROLLER_MAX_BUTTONS; i++) { (void) snprintf(string, sizeof(string) - 1, "button_%d_type", i); cp->button[i].type = get_config_int(name, string, 0); switch(cp->button[i].type) { case EOF_CONTROLLER_BUTTON_TYPE_KEY: { (void) snprintf(string, sizeof(string) - 1, "button_%d_key", i); cp->button[i].key = get_config_int(name, string, 0); break; } case EOF_CONTROLLER_BUTTON_TYPE_JOYBUTTON: { (void) snprintf(string, sizeof(string) - 1, "button_%d_controller", i); cp->button[i].joy = get_config_int(name, string, 0); (void) snprintf(string, sizeof(string) - 1, "button_%d_key", i); cp->button[i].key = get_config_int(name, string, 0); break; } case EOF_CONTROLLER_BUTTON_TYPE_JOYAXIS: { (void) snprintf(string, sizeof(string) - 1, "button_%d_controller", i); cp->button[i].joy = get_config_int(name, string, 0); (void) snprintf(string, sizeof(string) - 1, "button_%d_axis", i); cp->button[i].index = get_config_int(name, string, 0); (void) snprintf(string, sizeof(string) - 1, "button_%d_direction", i); cp->button[i].d = get_config_int(name, string, 0); (void) snprintf(string, sizeof(string) - 1, "button_%d_key", i); cp->button[i].key = get_config_int(name, string, 0); break; } } } cp->delay = get_config_int(name, "delay", 0); }
void eof_song_delete_text_event(EOF_SONG * sp, unsigned long event) { unsigned long i; eof_log("eof_song_delete_text_event() entered", 1); if(sp) { free(sp->text_event[event]); for(i = event; i < sp->text_events - 1; i++) { sp->text_event[i] = sp->text_event[i + 1]; } sp->text_events--; eof_cleanup_beat_flags(sp); //Rebuild event flags for all beats to ensure they're valid } }
EOF_WINDOW * eof_window_create(int x, int y, int w, int h, BITMAP * bp) { EOF_WINDOW * wp = NULL; if(bp == NULL) return NULL; eof_log("eof_window_create() entered", 1); wp = malloc(sizeof(EOF_WINDOW)); if(wp) { wp->x = x; wp->y = y; wp->w = w; wp->h = h; wp->screen = create_sub_bitmap(bp, x, y, w, h); } return wp; }
int eof_undo_load_state(const char * fn) { EOF_SONG * sp = NULL; PACKFILE * fp = NULL; char rheader[16] = {0}; int old_eof_silence_loaded = eof_silence_loaded; //Retain this value, since it is destroyed by eof_destroy_song() eof_log("eof_undo_load_state() entered", 1); if(fn == NULL) { return 0; } fp = pack_fopen(fn, "r"); if(!fp) { return 0; } if(pack_fread(rheader, 16, fp) != 16) { return 0; //Return error if 16 bytes cannot be read } sp = eof_create_song(); //Initialize an empty chart if(sp == NULL) { return 0; } if(!eof_load_song_pf(sp, fp)) { //If loading the undo state fails allegro_message("Failed to perform undo"); eof_destroy_song(sp); return 0; //Return failure } if(eof_song) { eof_destroy_song(eof_song); //Destroy the chart that is open } eof_song = sp; //Replacing it with the loaded undo state eof_silence_loaded = old_eof_silence_loaded; //Restore the status of whether chart audio is loaded (void) pack_fclose(fp); return 1; }
void eof_mix_start(int speed) { unsigned long i; eof_log("eof_mix_start() entered", 1); eof_mix_next_clap = -1; eof_mix_next_metronome = -1; for(i = 0; i < EOF_MIX_MAX_CHANNELS; i++) { eof_voice[i].sp = NULL; eof_voice[i].pos = 0; eof_voice[i].fpos = 0.0; eof_voice[i].playing = 0; eof_voice[i].volume = 100; //Default to 100% volume eof_voice[i].multiplier = 1.0; //Default to 100% volume } eof_voice[0].volume = eof_clap_volume; //Put the clap volume into effect eof_voice[0].multiplier = sqrt(eof_clap_volume/100.0); //Store this math so it only needs to be performed once eof_voice[1].volume = eof_tick_volume; //Put the tick volume into effect eof_voice[1].multiplier = sqrt(eof_tick_volume/100.0); //Store this math so it only needs to be performed once eof_voice[2].volume = eof_tone_volume; //Put the tone volume into effect eof_voice[2].multiplier = sqrt(eof_tone_volume/100.0); //Store this math so it only needs to be performed once eof_voice[3].volume = eof_percussion_volume; //Put the percussion volume into effect eof_voice[3].multiplier = sqrt(eof_percussion_volume/100.0); //Store this math so it only needs to be performed once eof_mix_speed = speed; eof_mix_speed_ticker = 0; eof_mix_sample_count = eof_mix_msec_to_sample(alogg_get_pos_msecs_ogg(eof_music_track), alogg_get_wave_freq_ogg(eof_music_track)); eof_mix_sample_increment = (1.0) * (44100.0 / (double)alogg_get_wave_freq_ogg(eof_music_track)); eof_mix_start_helper(); for(i = 1; i < eof_song->tracks; i++) { //Pre-process all tracks so that switching tracks during playback doesn't cause the playback to lag eof_determine_phrase_status(eof_song, i); } }
int EOF_EXPORT_TO_LC(EOF_VOCAL_TRACK * tp, char *outputfilename, char *string2, int format) { unsigned long linectr = 0, lyrctr = 0, lastlyrtime = 0, linestart = 0, lineend = 0; unsigned char pitch = 0; FILE *outf = NULL; //Used to open output file FILE *pitchedlyrics = NULL; //Used to open output pitched lyric fle char *vrhythmid = NULL; EOF_PHRASE_SECTION temp; //Used to store the first lyric line in the project, which gets overridden with one covering all lyrics during RS1 export unsigned long original_lines; char *tempoutputfilename = "lyrics.temp"; eof_log("EOF_EXPORT_TO_LC() entered", 1); if((tp == NULL) || (outputfilename == NULL) || (tp->lyrics == 0)) return -1; //Return failure //Initialize variables InitLyrics(); //Initialize all variables in the Lyrics structure InitMIDI(); //Initialize all variables in the MIDI structure qsort(tp->line, (size_t)tp->lines, sizeof(EOF_PHRASE_SECTION), eof_song_qsort_phrase_sections); //Sort the lyric lines temp = tp->line[0]; //Preserve the original lyric line information original_lines = tp->lines; //Set export-specific settings if(format == SCRIPT_FORMAT) { Lyrics.grouping = 2; //Enable line grouping for script.txt export Lyrics.nohyphens = 3; //Disable hyphen output Lyrics.noplus = 1; //Disable plus output Lyrics.filter = DuplicateString("^=%#/"); //Use default filter list Lyrics.defaultfilter = 1; //Track that the above string will need to be freed } else if((format == RS_FORMAT) || (format == RS2_FORMAT)) { Lyrics.noplus = 1; //Disable plus output Lyrics.filter = DuplicateString("^=%#/"); //Use default filter list Lyrics.defaultfilter = 1; //Track that the above string will need to be freed if((format == RS_FORMAT) || (!tp->lines)) { //If exporting to Rocksmith 1 format or if the lyrics don't have any lines defined tp->lines = 0; //Temporarily disregard any existing lyric lines (void) eof_vocal_track_add_line(tp, 0, tp->lyric[tp->lyrics - 1]->pos + 1, 0xFF); //Create a single line encompassing all lyrics } } else if(format == PLAIN_FORMAT) { //This format option is meant to invoke script export with the plain flag set and filtering enabled Lyrics.nohyphens = 3; //Disable hyphen output Lyrics.noplus = 1; //Disable plus output Lyrics.filter = DuplicateString("^=%#/"); //Use default filter list Lyrics.defaultfilter = 1; //Track that the above string will need to be freed format = SCRIPT_FORMAT; Lyrics.plain = 1; Lyrics.grouping = 2; //Enable line grouping for script.txt export } //Import lyrics from EOF structure lyrctr = 0; //Begin indexing into lyrics from the very first lastlyrtime = 0; //First lyric is expected to be greater than or equal to this timestamp for(linectr = 0; linectr < (unsigned long)tp->lines; linectr++) { //For each line of lyrics in the EOF structure linestart = (tp->line[linectr]).start_pos; lineend = (tp->line[linectr]).end_pos; if(linestart > lineend) //If the line starts after it ends { ReleaseMemory(1); return -1; //Return failure } if(lyrctr < tp->lyrics) //If there are lyrics remaining CreateLyricLine(); //Initialize new line of lyrics if((tp->line[linectr]).flags & EOF_LYRIC_LINE_FLAG_OVERDRIVE) //If this line is overdrive Lyrics.overdrive_on = 1; else Lyrics.overdrive_on = 0; while(lyrctr < tp->lyrics) { //For each lyric if((tp->lyric[lyrctr])->text[0] != '\0') { //If this lyric's text isn't an empty string if((tp->lyric[lyrctr])->pos < lastlyrtime) //If this lyric precedes the previous lyric { (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tLogic error while preparing lyrics for export to file \"%s\"", tempoutputfilename); eof_log(eof_log_string, 1); ReleaseMemory(1); return -1; //Return failure } if((tp->lyric[lyrctr])->pos < linestart) //If this lyric precedes the beginning of the line { (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tWarning: Lyric \"%s\" at %lums is outside of defined lyric lines", tp->lyric[lyrctr]->text, tp->lyric[lyrctr]->pos); eof_log(eof_log_string, 1); CreateLyricLine(); //Initialize new line of lyrics } if((tp->lyric[lyrctr])->pos > lineend) //If this lyric is placed beyond the end of this line { break; //Break from this while loop to have another line created } pitch = (tp->lyric[lyrctr])->note; //Store the lyric's pitch if((tp->lyric[lyrctr])->note == 0) //Remap EOF's pitchless value to FLC's pitchless value pitch = PITCHLESS; if(!Lyrics.line_on) //If a lyric line is not in progress CreateLyricLine(); //Force one to be before adding the next lyric AddLyricPiece((tp->lyric[lyrctr])->text, (tp->lyric[lyrctr])->pos, (tp->lyric[lyrctr])->pos+(tp->lyric[lyrctr])->length, pitch, 0); //Add the lyric to the Lyrics structure if((Lyrics.lastpiece != NULL) && (Lyrics.lastpiece->lyric[strlen(Lyrics.lastpiece->lyric)-1] == '-')) //If the piece that was just added ended in a hyphen Lyrics.lastpiece->groupswithnext = 1; //Set its grouping status }//If this lyric's text isn't an empty string lyrctr++; //Advance to next lyric } ForceEndLyricLine(); //End the current line of lyrics } if(Lyrics.piececount == 0) //No lyrics imported { ReleaseMemory(1); return 0; //Return no lyrics found } //Load chart tags if(eof_song->tags->artist[0] != '\0') Lyrics.Artist = DuplicateString(eof_song->tags->artist); if(eof_song->tags->title[0] != '\0') Lyrics.Title = DuplicateString(eof_song->tags->title); if(eof_song->tags->frettist[0] != '\0') Lyrics.Editor = DuplicateString(eof_song->tags->frettist); if(eof_song->tags->album[0] != '\0') Lyrics.Album = DuplicateString(eof_song->tags->album); PostProcessLyrics(); //Perform hyphen and grouping validation/handling Lyrics.outfilename = tempoutputfilename; Lyrics.out_format = format; //If the export format is MIDI-based, write a MIDI file header and a MIDI track (track 0) specifying a tempo of 120BPM if((Lyrics.out_format == MIDI_FORMAT) || (Lyrics.out_format == VRHYTHM_FORMAT) || (Lyrics.out_format == SKAR_FORMAT) || (Lyrics.out_format == KAR_FORMAT)) { outf = fopen_err(Lyrics.outfilename,"wb"); //These are binary formats Write_Default_Track_Zero(outf); } //Export lyrics switch(Lyrics.out_format) { case SCRIPT_FORMAT: //Export as script.txt format file outf = fopen_err(Lyrics.outfilename,"wt"); //Script.txt is a text format Export_Script(outf); break; case VL_FORMAT: //Export as VL format file outf = fopen_err(Lyrics.outfilename,"wb"); //VL is a binary format Export_VL(outf); break; case MIDI_FORMAT: //Export as MIDI format file. Default export track is "PART VOCALS" if(string2 == NULL) //If a destination track name wasn't given Lyrics.outputtrack = DuplicateString("PART VOCALS"); //Write track name as PART VOCALS by default else Lyrics.outputtrack = DuplicateString(string2); Export_MIDI(outf); break; case USTAR_FORMAT: //Export as UltraStar format file outf = fopen_err(Lyrics.outfilename,"wt"); //UltraStar is a text format Export_UStar(outf); break; case LRC_FORMAT: //Export as simple LRC case ELRC_FORMAT: //Export as extended LRC outf = fopen_err(Lyrics.outfilename,"wt"); //LRC is a text format Export_LRC(outf); break; case VRHYTHM_FORMAT: //Export as Vocal Rhythm (MIDI and text file) if(string2 == NULL) //If a pitched lyric file wasn't given { fclose_err(outf); return -1; //Return failure } pitchedlyrics = fopen_err(string2,"wt"); //Pitched lyrics is a text format vrhythmid = DuplicateString("G4"); Export_Vrhythm(outf, pitchedlyrics, vrhythmid); fflush_err(pitchedlyrics); //Commit any pending pitched lyric writes to file fclose_err(pitchedlyrics); //Close pitched lyric file free(vrhythmid); break; case SKAR_FORMAT: //Export as Soft Karaoke. Default export track is "Words" if(string2 == NULL) //If a destination track name wasn't given Lyrics.outputtrack = DuplicateString("Words"); //Write track name as "Words" by default else Lyrics.outputtrack = DuplicateString(string2); Export_SKAR(outf); break; case KAR_FORMAT: //Export as unofficial KAR. Default export track is "Melody" if(Lyrics.outputtrack == NULL) { (void) puts("\aNo ouput track name for KAR file was given. A track named \"Melody\" will be used by default"); Lyrics.outputtrack = DuplicateString("Melody"); } Export_MIDI(outf); break; case RS_FORMAT: //Export as Rocksmith XML outf = fopen_err(Lyrics.outfilename,"wt"); //XML is a text format Lyrics.rocksmithver = 1; Export_RS(outf); break; case RS2_FORMAT: //Export as Rocksmith 2 XML outf = fopen_err(Lyrics.outfilename,"wt"); //XML is a text format Lyrics.rocksmithver = 2; Export_RS(outf); break; default: (void) puts("Unexpected error in export switch\nAborting"); exit_wrapper(4); break; } if((Lyrics.out_format == MIDI_FORMAT) || (Lyrics.out_format == VRHYTHM_FORMAT) || (Lyrics.out_format == SKAR_FORMAT) || (Lyrics.out_format == KAR_FORMAT)) { //Update the MIDI header to reflect the number of MIDI tracks written to file for all applicable export formats fseek_err(outf, 10, SEEK_SET); //The number of tracks is 10 bytes in from the start of the file header fputc_err(MIDIstruct.trackswritten>>8, outf); fputc_err(MIDIstruct.trackswritten&0xFF, outf); }
int EOF_IMPORT_VIA_LC(EOF_VOCAL_TRACK *tp, struct Lyric_Format **lp, int format, char *inputfilename, char *string2) { char * returnedfn = NULL; //Return string from dialog window FILE *inf; //Used to open the input file struct Lyric_Format *detectionlist; unsigned long i; int jumpcode = 0; eof_log("EOF_IMPORT_VIA_LC() entered", 1); //Validate parameters if((tp == NULL) || (inputfilename == NULL)) return 0; //Return failure if((format == 0) && (lp == NULL)) return 0; //Return failure //Perform detection logic InitLyrics(); //Initialize all variables in the Lyrics structure InitMIDI(); //Initialize all variables in the MIDI structure if(format == 0) //Auto-detect lyric format { detectionlist=DetectLyricFormat(inputfilename); if(detectionlist == NULL) return 0; //Return invalid lyric file if(detectionlist->format == PITCHED_LYRIC_FORMAT) { //If the detection format is Pitched Lyrics, the user must specify the corresponding Vocal Rhythm MIDI *lp=detectionlist; //Return the detected lyric information via the lp pointer return -1; //Return prompt for user selection } if(detectionlist->next != NULL) { //If there was more MIDI track with lyrics, the user must specify which track to import *lp=detectionlist; //Return the detected lyric information via the lp pointer return -2; //Return prompt for user selection } Lyrics.in_format=detectionlist->format; //Format to import if(detectionlist->track != NULL) Lyrics.inputtrack=detectionlist->track; //Track to import from DestroyLyricFormatList(detectionlist); //Deallocate the linked list returned by DetectLyricFormat() detectionlist=NULL; } else //Import specific format { Lyrics.in_format=format; if(Lyrics.in_format == KAR_FORMAT) { //If this is a format for which string2 (pitched file or track name) must be specified if(string2 == NULL) //If the track name to import is not given return 0; //Return failure Lyrics.inputtrack=DuplicateString(string2); //Make a duplicate, so its de-allocation won't affect calling function } else if(Lyrics.in_format == PITCHED_LYRIC_FORMAT) { //If importing Pitched Lyrics, user must provide the Vocal Rhythm MIDI returnedfn = ncd_file_select(0, eof_filename, "Select Vocal Rhythm MIDI", eof_filter_midi_files); eof_clear_input(); if(!returnedfn) return 0; //Return error or user canceled } } Lyrics.infilename=DuplicateString(inputfilename); //Make a duplicate, so it's de-allocation won't affect calling function jumpcode=setjmp(jumpbuffer); //Store environment/stack/etc. info in the jmp_buf array if(jumpcode!=0) //if program control returned to the setjmp() call above returning any nonzero value { (void) puts("Assert() handled sucessfully!"); free(Lyrics.infilename); Lyrics.infilename = NULL; ReleaseMemory(1); //Release memory allocated during lyric import return 0; //Return error } //Import lyrics switch(Lyrics.in_format) { case SCRIPT_FORMAT: //Load script.txt format file as input inf=fopen_err(Lyrics.infilename,"rt"); //Script is a text format Script_Load(inf); break; case VL_FORMAT: //Load VL format file as input inf=fopen_err(Lyrics.infilename,"rb"); //VL is a binary format VL_Load(inf); break; case MIDI_FORMAT: //Load MIDI format file as input if(string2 == NULL) //If no track name was given Lyrics.inputtrack=DuplicateString("PART VOCALS"); //Default to PART VOCALS else Lyrics.inputtrack=DuplicateString(string2); //Make a duplicate, so its de-allocation won't affect calling function inf=fopen_err(Lyrics.infilename,"rb"); //MIDI is a binary format Parse_Song_Ini(Lyrics.infilename,1,1); //Load ALL tags from song.ini first, as the delay tag will affect timestamps MIDI_Load(inf,Lyric_handler,0); //Call MIDI_Load, specifying the new KAR-compatible Lyric Event handler break; case USTAR_FORMAT: //Load UltraStar format file as input inf=fopen_err(Lyrics.infilename,"rt"); //UltraStar is a text format UStar_Load(inf); break; case LRC_FORMAT: //Load LRC format file as input case ELRC_FORMAT: inf=fopen_err(Lyrics.infilename,"rt"); //LRC is a text format LRC_Load(inf); break; case VRHYTHM_FORMAT: //Load vocal rhythm (MIDI) and pitched lyrics inf=fopen_err(returnedfn,"rb"); //Vrhythm is a binary format VRhythm_Load(inputfilename,returnedfn,inf); break; case PITCHED_LYRIC_FORMAT: inf=fopen_err(returnedfn,"rb"); //Vrhythm is a binary format VRhythm_Load(eof_filename,returnedfn,inf); break; case KAR_FORMAT: //Load KAR MIDI file inf=fopen_err(Lyrics.infilename,"rb"); //KAR is a binary format MIDI_Load(inf,Lyric_handler,0); //Call MIDI_Load, specifying the new KAR-compatible Lyric Event handler break; case SKAR_FORMAT: //Load SKAR MIDI file inf=fopen_err(Lyrics.infilename,"rb"); //KAR is a binary format Lyrics.inputtrack=DuplicateString("Words"); MIDI_Load(inf,SKAR_handler,0); //Call MIDI_Load, specifying the Simple Karaoke Event handler EndLyricLine(); //KAR files do not mark the end of the last line of lyrics break; case ID3_FORMAT: //Load MP3 ID3 tag inf=fopen_err(Lyrics.infilename,"rb"); //MP3 is a binary format ID3_Load(inf); break; case SRT_FORMAT: //Load SRT file inf=fopen_err(Lyrics.infilename,"rt"); //SRT is a text format SRT_Load(inf); break; case XML_FORMAT: //Load XML file inf=fopen_err(Lyrics.infilename,"rt"); //XML is a text format XML_Load(inf); break; case C9C_FORMAT: //Load JamBand file inf=fopen_err(Lyrics.infilename,"rt"); //JamBand is a text format JB_Load(inf); break; case RS_FORMAT: //Load Rocksmith XML file case RS2_FORMAT: inf=fopen_err(Lyrics.infilename,"rt"); //Rocksmith XML is a text format RS_Load(inf); break; default: return 0; //Return failure }//switch(Lyrics.in_format) free(Lyrics.infilename); Lyrics.infilename = NULL; //Validate imported lyrics if((Lyrics.piececount == 0) || (MIDI_Lyrics.head != NULL)) //If the imported MIDI track had no valid lyrics or otherwise was incorrectly formatted { ReleaseMemory(1); //Release memory allocated during lyric import fclose_err(inf); return 0; //Return no EOF lyric structure } PostProcessLyrics(); //Perform validation of pointers, counters, etc. if(Lyrics.pitch_tracking) { //Only perform input pitch validation and remapping if the import lyrics had pitch information RemapPitches(); //Ensure pitches are within the correct range (except for pitchless lyrics) } //Delete any existing lyrics and lines for(i = 0; i < tp->lyrics; i++) { free(tp->lyric[i]); } tp->lyrics = 0; tp->lines = 0; fclose_err(inf); //Ensure this file gets closed inf=NULL; if(EOF_TRANSFER_FROM_LC(tp,&Lyrics) != 0) //Pass the Lyrics global variable by reference { ReleaseMemory(1); //Release memory allocated during lyric import return 0; //Return error (failed to import into EOF lyric structure) } ReleaseMemory(1); //Release memory allocated during lyric import return 1; //Return finished EOF lyric structure }
int EOF_TRANSFER_FROM_LC(EOF_VOCAL_TRACK * tp, struct _LYRICSSTRUCT_ * lp) { struct Lyric_Line *curline; //Conductor of the lyric line linked list struct Lyric_Piece *curpiece; //Conductor of the lyric piece linked list EOF_LYRIC *temp; //Pointer returned by eof_vocal_track_add_lyric() unsigned long start=0; //Used to track the start position of each line char startfound=0; //Used to help skip adding vocal percussion notes to lyric lines char overdrive=0; //Used to track the overdrive status of a lyric line eof_log("EOF_TRANSFER_FROM_LC() entered", 1); if((tp == NULL) || (lp == NULL)) return -1; //Return error (invalid structure pointers) curline=lp->lines; //Point line conductor to first lineof lyrics in the Lyrics structure while(curline != NULL) { //For each line of lyrics startfound = 0; overdrive = 0; curpiece=curline->pieces; //Point lyric conductor to first lyric in this line while(curpiece != NULL) { //For each lyric in this line if((startfound == 0) && (curpiece->pitch != VOCALPERCUSSION)) { startfound = 1; start=curpiece->start; //Store the timestamp of the first non vocal percussion note in this line } if(curpiece->overdrive) //If this lyric is overdrive overdrive = 1; temp=eof_vocal_track_add_lyric(tp); //Add a new lyric to EOF structure if(temp == NULL) return -1; //Return error (out of memory) if((curpiece->pitch == PITCHLESS) || (!lp->pitch_tracking)) //If this lyric has no defined pitch, or all imported lyrics have the same pitch temp->note=0; //Store with EOF's notation of a non defined pitch else temp->note=(char)curpiece->pitch; //curpiece->pitch is an unsigned char between the values of 0 and 127 temp->pos=curpiece->start; temp->length=(long)curpiece->duration; //curpiece->duration is an unsigned long value, hopefully this won't cause problems if(ustrlen(curpiece->lyric) > EOF_MAX_LYRIC_LENGTH) return -1; //Return error (lyric too large to store in EOF's array) if(curpiece->pitch == VOCALPERCUSSION) (void) ustrcpy(temp->text, ""); //Copy an empty string for a vocal percussion note else (void) ustrcpy(temp->text,curpiece->lyric); if((curpiece->next == NULL) && startfound) { //If this was the last lyric for this line, and at least one non vocal percussion note was found (void) eof_vocal_track_add_line(tp,start,curpiece->start + curpiece->duration, 0xFF); //Add the lyric line definition to the EOF structure if(overdrive && (tp->lines > 0)) //If this line had any overdriven lyrics tp->line[tp->lines-1].flags |= EOF_LYRIC_LINE_FLAG_OVERDRIVE; //Mark the line for overdrive } curpiece=curpiece->next; //Point to next lyric in the line } curline=curline->next; //Point to next line of lyrics } eof_log("\tLyrics imported", 1); return 0; //Return success }
int save_wav_with_silence_appended(const char * fn, SAMPLE * sp, unsigned long ms) { unsigned long i, samples, channels, index = 0; SAMPLE * silence, * combined; int retval; eof_log("save_wav_with_silence_appended() entered", 1); if(!fn || !sp) return 0; //Invalid parameters if(!ms) { //If the calling function specified writing the WAV with no silence appended return save_wav(fn, sp); //Write the audio normally } //Generate the silent audio to conform to the input sample's specifications samples = (double)ms * (double)sp->freq / 1000.0; channels = sp->stereo ? 2 : 1; silence = create_sample(sp->bits, sp->stereo, sp->freq, samples); if(!silence) return 0; //Return failure if(sp->bits == 8) { for(i = 0; i < samples * channels; i++) { ((unsigned char *)(silence->data))[i] = 0x80; } } else { for(i = 0; i < samples * channels; i++) { ((unsigned short *)(silence->data))[i] = 0x8000; } } //Combine the input audio and silence combined = create_sample(sp->bits, sp->stereo, sp->freq, sp->len + samples); if(!combined) { free(silence); return 0; //Return failure } if(sp->bits == 8) { for(i = 0; i < sp->len * channels; i++) { //For each sample in the input audio ((unsigned char *)(combined->data))[index++] = ((unsigned char *)(sp->data))[i]; //Copy it into the combined audio sample } for(i = 0; i < silence->len * channels; i++) { //For each sample in the silent audio ((unsigned char *)(combined->data))[index++] = ((unsigned char *)(silence->data))[i]; //Copy it into the combined audio sample } } else { for(i = 0; i < sp->len * channels; i++) { //For each sample in the input audio ((unsigned short *)(combined->data))[index++] = ((unsigned short *)(sp->data))[i]; //Copy it into the combined audio sample } for(i = 0; i < silence->len * channels; i++) { //For each sample in the silent audio ((unsigned short *)(combined->data))[index++] = ((unsigned short *)(silence->data))[i]; //Copy it into the combined audio sample } } //Write the combined audio to file and return retval = save_wav(fn, combined); //Write the combined audio destroy_sample(combined); destroy_sample(silence); return retval; }
int eof_undo_load_state(const char * fn) { EOF_SONG * sp = NULL; PACKFILE * fp = NULL, * rfp = NULL; char rheader[16] = {0}; int old_eof_silence_loaded = eof_silence_loaded; //Retain this value, since it is destroyed by eof_destroy_song() char eof_recover_path[50]; eof_log("eof_undo_load_state() entered", 1); if(fn == NULL) { return 0; } fp = pack_fopen(fn, "r"); if(!fp) { return 0; } if(pack_fread(rheader, 16, fp) != 16) { (void) pack_fclose(fp); return 0; //Return error if 16 bytes cannot be read } sp = eof_create_song(); //Initialize an empty chart if(sp == NULL) { (void) pack_fclose(fp); return 0; } sp->tags->accurate_ts = 0; //For existing projects, this setting must be manually enabled in order to prevent unwanted alteration to beat timings (void) snprintf(eof_recover_path, sizeof(eof_recover_path) - 1, "%seof.recover", eof_temp_path_s); rfp = pack_fopen(eof_recover_path, "r"); //Open the recovery file to prevent eof_destroy_song() from deleting it if(!eof_load_song_pf(sp, fp)) { //If loading the undo state fails allegro_message("Failed to perform undo"); (void) pack_fclose(rfp); //Close this recovery file handle so that it will be deleted by the following call to eof_destroy_song() eof_destroy_song(sp); (void) pack_fclose(fp); return 0; //Return failure } (void) pack_fclose(fp); if(EOF_TRACK_PRO_GUITAR_B >= sp->tracks) { //If the chart loaded does not contain a bonus pro guitar track (a pre 1.8RC12 chart or a post 1.8RC12 chart with an empty bonus track during save) if(eof_song_add_track(sp, &eof_default_tracks[EOF_TRACK_PRO_GUITAR_B]) == 0) //Add a blank bonus pro guitar track { //If the track failed to be added allegro_message("Failed to perform undo"); (void) pack_fclose(rfp); //Close this recovery file handle so that it will be deleted by the following call to eof_destroy_song() eof_destroy_song(sp); return 0; //Return failure } } if(eof_song) { eof_undo_in_progress = 1; //Signal to eof_destroy_song() that the waveform and spectrogram are not to be destroyed along with the active project structure eof_destroy_song(eof_song); //Destroy the chart that is open eof_undo_in_progress = 0; } (void) pack_fclose(rfp); //Calls to eof_destroy_song() are finished, the recovery file handle can be released now eof_song = sp; //Replacing it with the loaded undo state eof_silence_loaded = old_eof_silence_loaded; //Restore the status of whether chart audio is loaded return 1; }
void eof_redo_apply(void) { char fn[1024] = {0}; char title[256] = {0}; unsigned long ctr; EOF_PRO_GUITAR_TRACK *tp = NULL; char tech_view_status[EOF_PRO_GUITAR_TRACKS_MAX] = {0}; //Tracks whether or not tech view was in effect for each of the pro guitar tracks, so this view's status can be restored after the redo eof_log("eof_redo_apply() entered", 1); if(eof_redo_count > 0) { if(eof_validate_temp_folder()) { //Ensure the correct working directory and presence of the temporary folder eof_log("\tCould not validate working directory and temp folder", 1); return; } //Determine whether each pro guitar track was in tech view for(ctr = 0; ctr < EOF_PRO_GUITAR_TRACKS_MAX; ctr++) { //For each pro guitar track in the project tp = eof_song->pro_guitar_track[ctr]; if(tp->note == tp->technote) { //If tech view was in effect for this track tech_view_status[ctr] = 1; } } strncpy(title, eof_song->tags->title, sizeof(title) - 1); //Backup the song title field, since if it changes as part of the redo, the Rocksmith WAV file should be deleted (void) eof_save_song(eof_song, eof_undo_filename[eof_undo_current_index]); eof_undo_current_index++; if(eof_undo_current_index >= EOF_MAX_UNDO) { eof_undo_current_index = 0; } (void) snprintf(fn, sizeof(fn) - 1, "%seof%03u.redo", eof_temp_path_s, eof_log_id); //Get the name of this EOF instance's redo file (void) eof_undo_load_state(fn); //And load it if(eof_redo_type == EOF_UNDO_TYPE_SILENCE) { (void) snprintf(fn, sizeof(fn) - 1, "%seof%03u.redo.ogg", eof_temp_path_s, eof_log_id); //Get the name of this EOF instance's redo OGG (void) eof_copy_file(fn, eof_loaded_ogg_name); //And save the current audio to that filename (void) eof_load_ogg(eof_loaded_ogg_name, 0); eof_delete_rocksmith_wav(); //Delete the Rocksmith WAV file since changing silence will require a new WAV file to be written eof_fix_waveform_graph(); eof_fix_spectrogram(); } if(strcmp(title, eof_song->tags->title)) { //If the song title changed as part of the redo, delete the Rocksmith WAV file, since changing the title will cause a new WAV file to be written eof_delete_rocksmith_wav(); } eof_undo_count++; if(eof_undo_count >= EOF_MAX_UNDO) { eof_undo_count = EOF_MAX_UNDO; } eof_redo_count = 0; eof_change_count++; if(eof_change_count == 0) { eof_changes = 0; } else { eof_changes = 1; } eof_init_after_load(1); //Perform various cleanup (void) eof_detect_difficulties(eof_song, eof_selected_track); eof_select_beat(eof_selected_beat); eof_fix_catalog_selection(); eof_scale_fretboard(0); //Recalculate the 2D screen positioning based on the current track //Restore tech view for each pro guitar track that had it in use before the redo operation for(ctr = 0; ctr < EOF_PRO_GUITAR_TRACKS_MAX; ctr++) { //For each pro guitar track in the project tp = eof_song->pro_guitar_track[ctr]; if(tech_view_status[ctr]) { //If tech view was in effect for this track eof_menu_pro_guitar_track_enable_tech_view(tp); } } eof_fix_window_title(); } }
/* saves a wave file to file pointer */ static int save_wav_fp(SAMPLE * sp, PACKFILE * fp) { size_t channels, bits, freq; size_t data_size; size_t samples; size_t i, n; void * pval = NULL; unsigned short *data; eof_log("save_wav_fp() entered", 1); if(!sp || !fp) { return 0; } channels = sp->stereo ? (size_t)2 : (size_t)1; bits = (size_t)sp->bits; samples = (size_t)sp->len; data_size = samples * channels * (bits / 8); n = samples * channels; freq = (size_t)sp->freq; (void) pack_fputs("RIFF", fp); (void) pack_iputl(36 + (long)data_size, fp); (void) pack_fputs("WAVE", fp); (void) pack_fputs("fmt ", fp); (void) pack_iputl(16, fp); (void) pack_iputw(1, fp); (void) pack_iputw((int)channels, fp); (void) pack_iputl((long)freq, fp); (void) pack_iputl((long)(freq * channels * (bits / 8)), fp); //ByteRate = SampleRate * NumChannels * BitsPerSample/8 (void) pack_iputw((int)(channels * (bits / 8)), fp); (void) pack_iputw((int)bits, fp); (void) pack_fputs("data", fp); (void) pack_iputl((long)data_size, fp); if(bits == 8) { pval = sp->data; (void) pack_fwrite(pval, (long)(samples * channels), fp); } else if(bits == 16) { data = (unsigned short *)sp->data; for (i = 0; i < n; i++) { (void) pack_iputw((short)(data[i] - 0x8000), fp); } } else { (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "Unknown audio depth (%lu) when saving wav ALLEGRO_FILE.", (unsigned long) bits); eof_log(eof_log_string, 1); return 0; } return 1; }
int eof_undo_add(int type) { char fn[1024] = {0}, temp[1024] = {0}; unsigned long ctr; eof_log("eof_undo_add() entered", 1); if(eof_undo_states_initialized == -1) { //The undo filename array couldn't be initialized previously return 0; } if(eof_validate_temp_folder()) { //Ensure the correct working directory and presence of the temporary folder eof_log("\tCould not validate working directory and temp folder", 1); return 0; } if(!eof_undo_states_initialized) { //Initialize the undo filename array for(ctr = 0; ctr < EOF_MAX_UNDO; ctr++) { //For each undo slot (void) snprintf(fn, sizeof(fn) - 1, "%seof%03u-%03lu.undo", eof_temp_path_s, eof_log_id, ctr); //Build the undo filename in the format of "eof#-#.undo", where the first number is the EOF ID eof_undo_filename[ctr] = malloc(sizeof(fn) + 1); if(eof_undo_filename[ctr] == NULL) { allegro_message("Error initializing undo system. Undo disabled"); eof_undo_states_initialized = -1; return 0; } strcpy(eof_undo_filename[ctr], fn); //Save the undo filename to the array } eof_undo_states_initialized = 1; } if((type == EOF_UNDO_TYPE_NOTE_LENGTH) && (eof_undo_last_type == EOF_UNDO_TYPE_NOTE_LENGTH)) { return 0; } if((type == EOF_UNDO_TYPE_LYRIC_NOTE) && (eof_undo_last_type == EOF_UNDO_TYPE_LYRIC_NOTE)) { return 0; } if((type == EOF_UNDO_TYPE_RECORD) && (eof_undo_last_type == EOF_UNDO_TYPE_RECORD)) { return 0; } if((type == EOF_UNDO_TYPE_TEMPO_ADJUST) && (eof_undo_last_type == EOF_UNDO_TYPE_TEMPO_ADJUST)) { return 0; } if(type == EOF_UNDO_TYPE_SILENCE) { (void) snprintf(fn, sizeof(fn) - 1, "%s%s.ogg", eof_temp_path_s, eof_undo_filename[eof_undo_current_index]); (void) eof_copy_file(eof_loaded_ogg_name, fn); } eof_undo_last_type = type; (void) eof_save_song(eof_song, eof_undo_filename[eof_undo_current_index]); eof_undo_type[eof_undo_current_index] = type; if(eof_recovery) { //If this EOF instance is maintaining auto-recovery files PACKFILE *fp; (void) snprintf(fn, sizeof(fn) - 1, "%seof.recover", eof_temp_path_s); fp = pack_fopen(fn, "w"); //Open the recovery definition file for writing if(fp) { //If the file opened (void) append_filename(temp, eof_song_path, eof_loaded_song_name, 1024); //Construct the full path to the project file (void) pack_fputs(eof_undo_filename[eof_undo_current_index], fp); //Write the undo file path (void) pack_fputs("\n", fp); //Write a newline character (void) pack_fputs(temp, fp); //Write the project path (void) pack_fclose(fp); } } eof_undo_current_index++; if(eof_undo_current_index >= EOF_MAX_UNDO) { eof_undo_current_index = 0; } eof_undo_count++; if(eof_undo_count >= EOF_MAX_UNDO) { eof_undo_count = EOF_MAX_UNDO; } eof_redo_count = 0; return 1; }
int eof_export_bandfuse(EOF_SONG * sp, char * fn, unsigned short *user_warned) { PACKFILE * fp; char buffer[600] = {0}, buffer2[600] = {0}; EOF_PRO_GUITAR_TRACK *tp; char restore_tech_view = 0; //If tech view is in effect, it is temporarily disabled so that the correct notes are exported unsigned long ctr, ctr2, ctr3, ctr4, ctr5, gemcount, bitmask, guitartracknum = 0, basstracknum = 0; eof_log("eof_export_bandfuse() entered", 1); if(!sp || !fn || !sp->beats || !user_warned) { eof_log("\tError saving: Invalid parameters", 1); return 0; //Return failure } fp = pack_fopen(fn, "w"); if(!fp) { eof_log("\tError saving: Cannot open file for writing", 1); return 0; //Return failure } //Write the song metadata (void) pack_fputs("<?xml version='1.0' encoding='UTF-8'?>\n", fp); (void) pack_fputs("<Bandfuse>\n", fp); (void) pack_fputs("<!-- " EOF_VERSION_STRING " -->\n", fp); //Write EOF's version in an XML comment expand_xml_text(buffer2, sizeof(buffer2) - 1, sp->tags->title, 64, 0); //Expand XML special characters into escaped sequences if necessary, and check against the maximum supported length of this field (void) snprintf(buffer, sizeof(buffer) - 1, " <title>%s</title>\n", buffer2); (void) pack_fputs(buffer, fp); expand_xml_text(buffer2, sizeof(buffer2) - 1, sp->tags->artist, 256, 0); //Replace any special characters in the artist song property with escape sequences if necessary (void) snprintf(buffer, sizeof(buffer) - 1, " <artistName>%s</artistName>\n", buffer2); (void) pack_fputs(buffer, fp); expand_xml_text(buffer2, sizeof(buffer2) - 1, sp->tags->album, 256, 0); //Replace any special characters in the album song property with escape sequences if necessary (void) snprintf(buffer, sizeof(buffer) - 1, " <albumName>%s</albumName>\n", buffer2); (void) pack_fputs(buffer, fp); expand_xml_text(buffer2, sizeof(buffer2) - 1, sp->tags->year, 32, 0); //Replace any special characters in the year song property with escape sequences if necessary (void) snprintf(buffer, sizeof(buffer) - 1, " <albumYear>%s</albumYear>\n", buffer2); (void) pack_fputs(buffer, fp); //Write tempo changes (void) pack_fputs(" <tempochanges>\n", fp); ctr = 0; //Begin with the first beat while(ctr < sp->beats) { //Until the last beat has been reached for(ctr2 = ctr + 1; ctr2 < sp->beats; ctr2++) { //For each beat that follows if(sp->beat[ctr]->ppqn != sp->beat[ctr2]->ppqn) { //If the following beat has a different tempo break; //Break from inner for loop } } if(ctr2 < sp->beats) { //If a beat with a different tempo was found, that beat is written as the end position of this tempo change (void) snprintf(buffer, sizeof(buffer) - 1, " <tempo start=\"%lu\" end=\"%lu\" tempo=\"%f\">\n", sp->beat[ctr]->pos, sp->beat[ctr2]->pos, 60000000.0 / (double)sp->beat[ctr]->ppqn); } else { //No remaining beats had a different tempo, the outer for loop's beat is written as the end position of this tempo change (void) snprintf(buffer, sizeof(buffer) - 1, " <tempo start=\"%lu\" end=\"%lu\" tempo=\"%f\">\n", sp->beat[ctr]->pos, sp->beat[ctr]->pos, 60000000.0 / (double)sp->beat[ctr]->ppqn); } (void) pack_fputs(buffer, fp); ctr = ctr2; //Advance the beat counter } (void) pack_fputs(" </tempochanges>\n", fp); for(ctr = 1; ctr < sp->tracks; ctr++) { //For each track if(ctr >= EOF_TRACKS_MAX) break; //Redundant bounds check to satisfy a false positive with Coverity if(sp->track[ctr]->track_format != EOF_PRO_GUITAR_TRACK_FORMAT) continue; //If this isn't a pro guitar/bass track, skip it if(ctr == EOF_TRACK_PRO_GUITAR_B) continue; //If this is the bonus Rocksmith arrangement, skip it tp = sp->pro_guitar_track[sp->track[ctr]->tracknum]; restore_tech_view = eof_menu_track_get_tech_view_state(sp, ctr); eof_menu_track_set_tech_view_state(sp, ctr, 0); //Disable tech view if applicable if(tp->notes) { //If this track is populated eof_determine_phrase_status(sp, ctr); //Update the trill and tremolo status of each note if(tp->arrangement != 4) { //If this isn't a bass track guitartracknum++; //Keep count of how many of this track type there have been (void) snprintf(buffer, sizeof(buffer) - 1, " <arrangement name=\"Guitar %lu (%s)\">\n", guitartracknum, sp->track[ctr]->name); (void) pack_fputs(buffer, fp); } else { //This is a bass track basstracknum++; //Keep count of how many of this track type there have been (void) snprintf(buffer, sizeof(buffer) - 1, " <arrangement name=\"Bass %lu (%s)\">\n", guitartracknum, sp->track[ctr]->name); (void) pack_fputs(buffer, fp); } (void) snprintf(buffer, sizeof(buffer) - 1, " <tuning string0=\"%d\" string1=\"%d\" string2=\"%d\" string3=\"%d\" string4=\"%d\" string5=\"%d\" />\n", tp->tuning[0], tp->tuning[1], tp->tuning[2], tp->tuning[3], tp->tuning[4], tp->tuning[5]); (void) pack_fputs(buffer, fp); (void) eof_detect_difficulties(sp, ctr); //Update eof_track_diff_populated_status[] to reflect all populated difficulties for this track for(ctr2 = 0; ctr2 < 6; ctr2++) { //For each of the first 6 difficulties unsigned long anchorcount; char anchorsgenerated = 0; //Is set to nonzero if fret hand positions are automatically generated for this difficulty and will have to be deleted if(!eof_track_diff_populated_status[ctr2]) continue; //If this difficulty is not populated, skip it (void) snprintf(buffer, sizeof(buffer) - 1, " <level difficulty=\"%lu\">\n", ctr2); (void) pack_fputs(buffer, fp); //Count the number of note gems in this track difficulty for(ctr3 = 0, gemcount = 0; ctr3 < tp->notes; ctr3++) { //For each note in the track if(eof_get_note_type(sp, ctr, ctr3) == ctr2) { //If the note is in this difficulty gemcount += eof_note_count_rs_lanes(sp, ctr, ctr3, 2); //Add this note's number of non-ghosted gems to a counter } } (void) snprintf(buffer, sizeof(buffer) - 1, " <notes count=\"%lu\">\n", gemcount); (void) pack_fputs(buffer, fp); //Generate fret hand positions if there are none for this difficulty for(ctr3 = 0, anchorcount = 0; ctr3 < tp->handpositions; ctr3++) { //For each hand position defined in the track if(tp->handposition[ctr3].difficulty == ctr2) { //If the hand position is in this difficulty anchorcount++; } } if(!anchorcount) { //If there are no anchors in this track difficulty, automatically generate them if((tp->arrangement != 4) || eof_warn_missing_bass_fhps) { //Don't warn about missing FHPs in bass arrangements if user disabled that preference if((*user_warned & 1) == 0) { //If the user wasn't alerted that one or more track difficulties have no fret hand positions defined allegro_message("Warning: At least one track difficulty has no fret hand positions defined. They will be created automatically."); *user_warned |= 1; } } eof_fret_hand_position_list_dialog_undo_made = 1; //Ensure no undo state is written during export eof_generate_efficient_hand_positions(sp, ctr, ctr2, 0, 0); //Generate the fret hand positions for the track difficulty being currently written anchorsgenerated = 1; } for(ctr3 = 0, anchorcount = 0; ctr3 < tp->handpositions; ctr3++) //Re-count the hand positions { //For each hand position defined in the track if(tp->handposition[ctr3].difficulty == ctr) { //If the hand position is in this difficulty anchorcount++; } } //Write the notes to XML for(ctr3 = 0; ctr3 < tp->notes; ctr3++) { //For each note in the track if(eof_get_note_type(sp, ctr, ctr3) != ctr2) continue; //If the note is not in this difficulty, skip it for(ctr4 = 0, bitmask = 1; ctr4 < tp->numstrings; ctr4++, bitmask <<= 1) { //For each string in this track EOF_RS_TECHNIQUES tech = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; unsigned long notepos; unsigned long fret, finger; //The fret and finger numbers used to play the gem on this string unsigned long trill_start, trill_stop; //The start and stop positions of the note's trill phrase int trillwith = -1; //If the note is in a trill phrase, this is set to the fret value of the next or previous gem on the string long next; unsigned long stringnum; //The converted string number (ie. low E string is string 6) char *sustainpadding; if(!(tp->note[ctr3]->note & bitmask) || (tp->note[ctr3]->ghost & bitmask)) continue; //If this string does not have a gem, or if it has one that is ghosted, skip it trill_start = trill_stop = tp->note[ctr3]->pos; //Initialize variables in order to track if the trill phrase can't be properly found if(tp->note[ctr3]->flags & EOF_NOTE_FLAG_IS_TRILL) { //If this note is in a trill phrase //Find which trill phrase it's in for(ctr5 = 0; ctr5 < tp->trills; ctr5++) { //For each trill phrase if((tp->trill[ctr5].difficulty == 0xFF) || (tp->trill[ctr5].difficulty == ctr2)) { //If this trill phrase applies to all difficulties or if it otherwise applies to this note's difficulty if((tp->trill[ctr5].start_pos <= tp->note[ctr3]->pos) && (tp->trill[ctr5].end_pos >= tp->note[ctr3]->pos)) { //If the note is inside this trill phrase trill_start = tp->trill[ctr5].start_pos; trill_stop = tp->trill[ctr5].end_pos; break; } } } //Determine if this is the first note in the trill phrase, and if so, what note it trills with if(trill_start != trill_stop) { //If a trill phrase was identified for(ctr5 = 0; ctr5 < tp->notes; ctr5++) { //For each note in the track if(eof_get_note_type(sp, ctr, ctr3) != ctr2) continue; //If the note is not in this difficulty, skip it if(tp->note[ctr5]->pos > trill_stop) break; //Stop looking at notes if the trill has already been passed if(tp->note[ctr5]->pos < trill_start) continue; //If the note is not within the trill phrase if(ctr5 != ctr3) { //If the note being written to XML is not this note break; //Don't export it with trill technique } next = eof_fixup_next_pro_guitar_note(tp, ctr5); if((next > 0) && (tp->note[next]->pos <= trill_stop)) { //If the next note in the track difficulty is also in this trill phrase if((tp->note[next]->note & bitmask) && ((tp->note[next]->frets[next] & 0x7F) > (tp->note[ctr3]->frets[ctr4] & 0x7F))) { //If the next note has a gem on the target string and its fret value is higher than that of the note being currently written to XML trillwith = tp->note[next]->frets[ctr4] & 0x7F; //This is the fret value being trilled with } } break; }//For each note in the track }//If a trill phrase was identified }//If this note is in a trill phrase (void) eof_get_rs_techniques(sp, ctr, ctr3, ctr4, &tech, 2, 1); //Determine techniques used by this note (honoring technotes where applicable) notepos = eof_get_note_pos(sp, ctr, ctr3); fret = tp->note[ctr3]->frets[ctr4] & 0x7F; //Get the fret value for this string (mask out the muting bit) finger = eof_pro_guitar_note_lookup_string_fingering(tp, ctr3, ctr4, 1); //Unless a more suitable fingering for this note can be determined, assume 1 (index) if(tech.length < 10) { //The sustain is one digit sustainpadding = " "; } else if(tech.length < 100) { //The sustain is two digits sustainpadding = " "; } else if(tech.length < 1000) { //The sustain is three digits sustainpadding = " "; } else { //It's four or more digits sustainpadding = ""; } fret += tp->capo; //Compensate for the the capo position if applicable stringnum = tp->numstrings - ctr4; //Convert to tab string numbering (low E is string 6 in a 6 string track, string 4 on a 4 string track, etc) (void) snprintf(buffer, sizeof(buffer) - 1, " <note time=\"%lu\" linkNext=\"%d\" bend=\"%lu\" fret=%s\"%lu\" hammerOn=\"%d\" harmonic=\"%d\" hopo=\"%d\" ignore=\"0\" mute=\"%d\" palmMute=\"%d\" pluck=\"%d\" pullOff=\"%d\" slap=\"%d\" slideTo=\"%ld\" string=\"%lu\" sustain=%s\"%lu\" tremolo=\"%d\" harmonicPinch=\"%d\" slideUnpitchTo=\"%ld\" trillwith=\"%d\" finger=\"%lu\" vibrato=\"%d\"/>\n", notepos, tech.linknext, tech.bendstrength_h, ((fret < 10) ? " " : ""), fret, tech.hammeron, tech.harmonic, tech.hopo, tech.stringmute, tech.palmmute, tech.pop, tech.pulloff, tech.slap, tech.slideto, stringnum, sustainpadding, tech.length, tech.tremolo, tech.pinchharmonic, tech.unpitchedslideto, trillwith, finger, tech.vibrato); (void) pack_fputs(buffer, fp); }//For each string in this track }//For each note in the track //Remove any automatically generated fret hand positions if(anchorsgenerated) { //If anchors were automatically generated for this track difficulty, remove them now for(ctr3 = tp->handpositions; ctr3 > 0; ctr3--) { //For each hand position defined in the track, in reverse order if(tp->handposition[ctr3 - 1].difficulty == ctr2) { //If the hand position is in this difficulty eof_pro_guitar_track_delete_hand_position(tp, ctr3 - 1); //Delete the hand position } } } (void) pack_fputs(" </notes>\n", fp); (void) pack_fputs(" </level>\n", fp); }//For each of the first 6 difficulties (void) pack_fputs(" </arrangement>\n", fp); }//If this track is populated eof_menu_track_set_tech_view_state(sp, ctr, restore_tech_view); //Re-enable tech view if applicable }//For each track (void) pack_fputs("</Bandfuse>\n", fp); //Cleanup (void) pack_fclose(fp); return 1; //Return success }
int eof_add_silence_recode_mp3(char * oggfn, unsigned long ms) { char sys_command[1024] = {0}; char backupfn[1024] = {0}; char wavfn[1024] = {0}; char soggfn[1024] = {0}; char mp3fn[1024] = {0}; SAMPLE * decoded = NULL; SAMPLE * combined = NULL; int bits; int stereo; int freq; unsigned long samples; int channels; unsigned long ctr,index; eof_log("eof_add_silence_recode_mp3() entered", 1); if(!oggfn || (ms == 0) || eof_silence_loaded) { return 21; //Return error: Invalid parameters } set_window_title("Adjusting Silence..."); /* back up original file */ (void) snprintf(backupfn, sizeof(backupfn) - 1, "%s.backup", oggfn); if(!exists(backupfn)) { (void) eof_copy_file(oggfn, backupfn); } /* decode MP3 */ (void) replace_filename(wavfn, eof_song_path, "decode.wav", 1024); (void) replace_filename(mp3fn, eof_song_path, "original.mp3", 1024); (void) uszprintf(sys_command, (int) sizeof(sys_command) - 1, "lame --decode \"%s\" \"%s\"", mp3fn, wavfn); (void) eof_system(sys_command); /* insert silence */ decoded = load_sample(wavfn); if(!decoded) { allegro_message("Error opening file.\nMake sure there are no Unicode or extended ASCII characters in this chart's file path."); return 22; //Return failure: Could not load decoded MP3 file } bits = decoded->bits; stereo = decoded->stereo; freq = decoded->freq; samples = (decoded->freq * ms) / 1000; //Calculate this manually instead of using msec_to_samples() because that function assumes the sample rate matches the current chart audio, and this may not be the case with the original MP3 file the user provided channels = stereo ? 2 : 1; combined = create_sample(bits,stereo,freq,samples+decoded->len); //Create a sample array long enough for the silence and the OGG file if(combined == NULL) { destroy_sample(decoded); return 23; //Return failure: Could not create a sample array for the combined audio } /* Add the PCM data for the silence */ if(bits == 8) { //Create 8 bit PCM data for(ctr=0,index=0;ctr < samples * channels;ctr++) { ((unsigned char *)(combined->data))[index++] = 0x80; } } else { //Create 16 bit PCM data for(ctr=0,index=0;ctr < samples * channels;ctr++) { ((unsigned short *)(combined->data))[index++] = 0x8000; } } /* Add the decoded OGG PCM data*/ if(bits == 8) { //Copy 8 bit PCM data for(ctr=0;ctr < decoded->len * channels;ctr++) { ((unsigned char *)(combined->data))[index++] = ((unsigned char *)(decoded->data))[ctr]; } } else { //Copy 16 bit PCM data for(ctr=0;ctr < decoded->len * channels;ctr++) { ((unsigned short *)(combined->data))[index++] = ((unsigned short *)(decoded->data))[ctr]; } } /* save combined WAV */ (void) replace_filename(wavfn, eof_song_path, "encode.wav", 1024); if(!save_wav(wavfn, combined)) { destroy_sample(decoded); //This is no longer needed destroy_sample(combined); //This is no longer needed return 24; //Return failure: Could not create the combined audio WAV file } /* destroy samples */ destroy_sample(decoded); //This is no longer needed destroy_sample(combined); //This is no longer needed /* encode the audio */ printf("%s\n%s\n", eof_song_path, wavfn); (void) replace_filename(soggfn, eof_song_path, "encode.ogg", 1024); #ifdef ALLEGRO_WINDOWS (void) uszprintf(sys_command, (int) sizeof(sys_command) - 1, "oggenc2 -o \"%s\" -b %d \"%s\"", soggfn, alogg_get_bitrate_ogg(eof_music_track) / 1000, wavfn); #else (void) uszprintf(sys_command, (int) sizeof(sys_command) - 1, "oggenc -o \"%s\" -b %d \"%s\"", soggfn, alogg_get_bitrate_ogg(eof_music_track) / 1000, wavfn); #endif (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tCalling oggenc as follows: %s", sys_command); eof_log(eof_log_string, 1); if(eof_system(sys_command)) { //If oggenc failed, retry again by specifying a quality level (specifying bitrate can fail in some circumstances) eof_log("\t\toggenc failed. Retrying by specifying a quality level instead of a target bitrate", 1); #ifdef ALLEGRO_WINDOWS (void) uszprintf(sys_command, (int) sizeof(sys_command) - 1, "oggenc2 -o \"%s\" -q 9 \"%s\"", soggfn, wavfn); #else (void) uszprintf(sys_command, (int) sizeof(sys_command) - 1, "oggenc -o \"%s\" -q 9 \"%s\"", soggfn, wavfn); #endif (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tCalling oggenc as follows: %s", sys_command); eof_log(eof_log_string, 1); if(eof_system(sys_command)) { //If oggenc failed again char tempfname[30] = {0}; char redirect[35] = {0}; if(eof_validate_temp_folder()) { //Ensure the correct working directory and presence of the temporary folder eof_log("\tCould not validate working directory and temp folder", 1); return 25; //Return failure: Could not validate cwd and temp folder } (void) snprintf(tempfname, sizeof(tempfname) - 1, "%soggenc.log", eof_temp_path_s); (void) snprintf(redirect, sizeof(redirect) - 1, " 2> %s", tempfname); (void) ustrzcat(sys_command, (int) sizeof(sys_command) - 1, redirect); //Append a redirection to the command to capture the output of oggenc if(eof_system(sys_command)) { //Run one last time to catch the error output (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tOggenc failed. Please see %s for any errors it gave.", tempfname); eof_log(eof_log_string, 1); eof_fix_window_title(); return 26; //Return failure: Could not encode combined audio } } } /* replace the current OGG file with the new file */ (void) eof_copy_file(soggfn, oggfn); //Copy encode.ogg to the filename of the original OGG /* clean up */ (void) replace_filename(wavfn, eof_song_path, "decode.wav", 1024); (void) delete_file(wavfn); //Delete decode.wav (void) replace_filename(wavfn, eof_song_path, "encode.wav", 1024); (void) delete_file(wavfn); //Delete encode.wav (void) delete_file(soggfn); //Delete encode.ogg if(eof_load_ogg(oggfn, 0)) { //If the combined audio was loaded eof_fix_waveform_graph(); eof_fix_spectrogram(); eof_fix_window_title(); eof_chart_length = eof_music_length; return 0; //Return success } eof_fix_window_title(); return 27; //Return error: Could not load new audio }
int eof_undo_apply(void) { char fn[1024] = {0}; char title[256] = {0}; unsigned long ctr; EOF_PRO_GUITAR_TRACK *tp = NULL; char tech_view_status[EOF_PRO_GUITAR_TRACKS_MAX] = {0}; //Tracks whether or not tech view was in effect for each of the pro guitar tracks, so this view's status can be restored after the undo eof_log("eof_undo_apply() entered", 1); if(eof_undo_count > 0) { //Determine whether each pro guitar track was in tech view for(ctr = 0; ctr < EOF_PRO_GUITAR_TRACKS_MAX; ctr++) { //For each pro guitar track in the project tp = eof_song->pro_guitar_track[ctr]; if(tp->note == tp->technote) { //If tech view was in effect for this track tech_view_status[ctr] = 1; } } strncpy(title, eof_song->tags->title, sizeof(title) - 1); //Backup the song title field, since if it changes as part of the undo, the Rocksmith WAV file should be deleted (void) snprintf(fn, sizeof(fn) - 1, "%seof%03u.redo", eof_temp_path, eof_log_id); //Include EOF's log ID in the redo name to almost guarantee it is uniquely named (void) eof_save_song(eof_song, fn); eof_redo_type = 0; eof_undo_current_index--; if(eof_undo_current_index < 0) { eof_undo_current_index = EOF_MAX_UNDO - 1; } (void) eof_undo_load_state(eof_undo_filename[eof_undo_current_index]); if(eof_undo_type[eof_undo_current_index] == EOF_UNDO_TYPE_NOTE_SEL) { (void) eof_menu_edit_deselect_all(); } if(eof_undo_type[eof_undo_current_index] == EOF_UNDO_TYPE_SILENCE) { (void) snprintf(fn, sizeof(fn) - 1, "%seof%03u.redo.ogg", eof_temp_path, eof_log_id); //Include EOF's log ID in the redo name to almost guarantee it is uniquely named (void) eof_copy_file(eof_loaded_ogg_name, fn); (void) snprintf(fn, sizeof(fn) - 1, "%s%s.ogg", eof_temp_path, eof_undo_filename[eof_undo_current_index]); (void) eof_copy_file(fn, eof_loaded_ogg_name); (void) eof_load_ogg(eof_loaded_ogg_name, 0); eof_delete_rocksmith_wav(); //Delete the Rocksmith WAV file since changing silence will require a new WAV file to be written eof_fix_waveform_graph(); eof_fix_spectrogram(); eof_redo_type = EOF_UNDO_TYPE_SILENCE; } if(strcmp(title, eof_song->tags->title)) { //If the song title changed as part of the undo, delete the Rocksmith WAV file, since changing the title will cause a new WAV file to be written eof_delete_rocksmith_wav(); } eof_undo_count--; eof_redo_count = 1; eof_change_count--; if(eof_change_count == 0) { eof_changes = 0; } else { eof_changes = 1; } eof_undo_last_type = 0; eof_init_after_load(1); //Perform various cleanup (void) eof_detect_difficulties(eof_song, eof_selected_track); eof_select_beat(eof_selected_beat); eof_fix_catalog_selection(); eof_scale_fretboard(0); //Recalculate the 2D screen positioning based on the current track //Restore tech view for each pro guitar track that had it in use before the undo operation for(ctr = 0; ctr < EOF_PRO_GUITAR_TRACKS_MAX; ctr++) { //For each pro guitar track in the project tp = eof_song->pro_guitar_track[ctr]; if(tech_view_status[ctr]) { //If tech view was in effect for this track eof_menu_pro_guitar_track_enable_tech_view(tp); } } eof_fix_window_title(); return 1; } return 0; }