private void submit_Click(object sender, EventArgs e) { if (int.TryParse(textBoxOctaveAdjust.Text, out int octaveAdjust)) { button1.Enabled = false; button2.Enabled = false; // iterate every file in the mordhau Save folder matching format [A-Za-z0-9]*\[[0-9]*\] // skip any named PartitionIndex // First, move them to a backup folder // Then parse each set... // Parse out each command and convert it to MidiNote // Create a collection of MidiNotes for each set/song // For all notes of type 1, increase the second number (ex 1320-14-1;) by 12*octaveAdjust // Run the collection back through the partition save routine // This is all divison is used for: // partition.Tempo = note.Tone / division; // return new Note() { Tick = note.Tick, Tone = note.Tone / division, Type = note.Type }; // So when I read the Tempo, NoteType 2, I can pass it straight back without sending through BuildNote // The rest I do need to send through BuildNote // But also be sure to set partition.Tempo // The save file requires: ([a-zA-Z0-9][a-zA-Z0-9 -]*[a-zA-Z0-9]) Regex filenameReg = new Regex(@"^([a-zA-Z0-9][a-zA-Z0-9 -]*[a-zA-Z0-9])\[([0-9]+)\].sav$"); // The first capture group is the song name, the second is the index Dictionary <string, string> fileContents = new Dictionary <string, string>(); // Dictionary of filename:contents // We are also using a dictionary of index to string, because we can't guarantee what order we read these in (or, we could I guess, IDK) string backupDir = Path.Combine(SaveManager.SaveFilePath, "PartitionBackup " + DateTime.Now.ToString("MM-dd-yyyy")); Directory.CreateDirectory(backupDir); try { foreach (var f in Directory.GetFiles(SaveManager.SaveFilePath)) { var filename = Path.GetFileName(f); var match = filenameReg.Match(filename); if (match.Success) { var songName = match.Groups[1].Value; int index = int.Parse(match.Groups[2].Value); if (songName != "PartitionIndex") { if (!fileContents.ContainsKey(songName)) { fileContents[songName] = ""; } // BOF: "SavedPartition StrProperty Ž Š " // EOF: " None " var contents = File.ReadAllText(f); int splitLocation = contents.IndexOf("SavedPartition\0\f\0\0\0StrProperty\0�\u0013\0\0\0\0\0\0\0�\u0013\0\0", StringComparison.InvariantCulture) + "SavedPartition StrProperty Ž Š ".Length; string songContent = contents.Substring(splitLocation); songContent = songContent.Replace("\0\u0005\0\0\0None\0\0\0\0", ""); fileContents[songName] += songContent; // Move it to a new backup folder File.Move(f, Path.Combine(backupDir, filename)); } } } } catch { MessageBox.Show("Failed to backup files. If a backup folder already exists in the Save Folder that should now open, rename, move, or delete it"); button1.Enabled = true; button2.Enabled = false; Process.Start(SaveManager.SaveFilePath); return; } // I think old lutemod saved the track name into the file or something, it's there on a few specific ones // This might be why dashes were disallowed, doing [^-;] would be great, but instead I'll put the naming thing and make it optional Regex tempoRegex = new Regex(@"^\|[a-zA-Z0-9]?[a-zA-Z0-9 -]*[a-zA-Z0-9]?;([0-9]+)\|"); Regex noteRegex = new Regex(@"([0-9]+)-([0-9]+)-([0-9]+);"); int lowNote = ConfigManager.GetIntegerProperty(PropertyItem.LowestPlayedNote); List <string> errorSongs = new List <string>(); foreach (var song in fileContents.Keys) { try { // Parse out each command and convert it to MidiNote // Create a collection of MidiNotes for each set/song // For all notes of type 1, increase the second number (ex 1320-14-1;) by 12*octaveAdjust // Run the collection back through the partition save routine List <LuteMod.Sequencing.Note> notes = new List <LuteMod.Sequencing.Note>(); string content = fileContents[song]; var tempoMatch = tempoRegex.Match(content); var converter = new LuteMod.Converter.MordhauConverter(); converter.Range = ConfigManager.GetIntegerProperty(PropertyItem.AvaliableNoteCount); converter.LowNote = lowNote; converter.IsConversionEnabled = true; //converter.SetDivision(player.sequence.Division); converter.AddTrack(); converter.SetPartitionTempo(int.Parse(tempoMatch.Groups[1].Value)); converter.SetDivision(1); var noteMatches = noteRegex.Matches(content); foreach (Match noteMatch in noteMatches) { int tone = int.Parse(noteMatch.Groups[2].Value); int noteType = int.Parse(noteMatch.Groups[3].Value); if (noteType == 1) { tone = tone + octaveAdjust * 12 + lowNote; } notes.Add(new LuteMod.Sequencing.Note() { Tick = int.Parse(noteMatch.Groups[1].Value), Tone = tone, Type = (LuteMod.Sequencing.NoteType)noteType }); } // result.Add(new LuteMod.Sequencing.Note() { Tick = tempTick, Tone = tempMessage.Data1 + offset, Type = LuteMod.Sequencing.NoteType.On }); converter.FillTrack(0, notes); // We let it re-process them to fit new ranges, which is why we re-added lowNote above - it needs it, and will remove it again // Interestingly, this also automatically moves things for our lute update, which is great SaveManager.WriteSaveFile(Path.Combine(SaveManager.SaveFilePath, song), converter.GetPartitionToString()); } catch { errorSongs.Add(song); } } if (errorSongs.Count > 0) { MessageBox.Show("Some songs caused exceptions while converting: " + string.Join("\n", errorSongs) + "\n\nYou can restore these from the backup folder that was opened"); Process.Start(backupDir); } else { //index.SaveIndex(); //PopulateIndexList(); Close(); MessageBox.Show("Successfully converted all songs"); } } else { MessageBox.Show("Please enter a value in the Octave Adjust field"); } }
private void savePartitionsButton_Click(object sender, EventArgs e) { if (tsm.MidiTracks.Values.Where(t => t.Active).Count() > 0) { var namingForm = new TrackNamingForm(Path.GetFileNameWithoutExtension(tsm.FileName)); namingForm.ShowDialog(this); if (namingForm.DialogResult == DialogResult.OK) { if (namingForm.textBoxPartName.Text == "Partition Name" || namingForm.textBoxPartName.Text.Trim() == "") { MessageBox.Show("Please name your partition"); } else { if (index.PartitionNames.Contains(namingForm.textBoxPartName.Text)) { MessageBox.Show("That name already exists"); } else { if (!Regex.IsMatch(namingForm.textBoxPartName.Text, "^([a-zA-Z0-9][a-zA-Z0-9 -]*[a-zA-Z0-9])$")) { MessageBox.Show("That name contains invalid characters"); } else { index.PartitionNames.Add(namingForm.textBoxPartName.Text); //if (trackConverter == null) //{ var converter = new LuteMod.Converter.MordhauConverter(); int firstInstrument = ConfigManager.GetIntegerProperty(PropertyItem.Instrument); // Step 1, load solo lute into track 0 - this profile should always exist // Actually, all of the first 4 instruments get loaded in, under the same ID we use in lutebot. Convenient. for (int i = 0; i < 4; i++) { int oldInstrument = ConfigManager.GetIntegerProperty(PropertyItem.Instrument); if (tsm.DataDictionary.ContainsKey(i)) { if (oldInstrument != i) { ConfigManager.SetProperty(PropertyItem.Instrument, i.ToString()); Instrument target = Instrument.Prefabs[i]; bool soundEffects = !target.Name.StartsWith("Mordhau", true, System.Globalization.CultureInfo.InvariantCulture); ConfigManager.SetProperty(PropertyItem.SoundEffects, soundEffects.ToString()); ConfigManager.SetProperty(PropertyItem.LowestNoteId, target.LowestSentNote.ToString()); ConfigManager.SetProperty(PropertyItem.AvaliableNoteCount, target.NoteCount.ToString()); ConfigManager.SetProperty(PropertyItem.NoteCooldown, target.NoteCooldown.ToString()); ConfigManager.SetProperty(PropertyItem.LowestPlayedNote, target.LowestPlayedNote.ToString()); ConfigManager.SetProperty(PropertyItem.ForbidsChords, target.ForbidsChords.ToString()); tsm.UpdateTrackSelectionForInstrument(oldInstrument); player.mordhauOutDevice.UpdateNoteIdBounds(); } converter.Range = ConfigManager.GetIntegerProperty(PropertyItem.AvaliableNoteCount); converter.LowNote = ConfigManager.GetIntegerProperty(PropertyItem.LowestPlayedNote); converter.IsConversionEnabled = true; converter.SetDivision(player.sequence.Division); converter.SetPartitionTempo(player.sequence.FirstTempo); converter.AddTrack(); converter.SetEnabledTracksInTrack(i, tsm.MidiTracks.Values.ToList()); converter.SetEnabledMidiChannelsInTrack(i, tsm.MidiChannels.Values.ToList()); converter.FillTrack(i, player.ExtractMidiContent()); } } SaveManager.WriteSaveFile(Path.Combine(SaveManager.SaveFilePath, namingForm.textBoxPartName.Text), converter.GetPartitionToString()); index.SaveIndex(); PopulateIndexList(); // And put the instrument back if (ConfigManager.GetIntegerProperty(PropertyItem.Instrument) != firstInstrument) { int oldInstrument = ConfigManager.GetIntegerProperty(PropertyItem.Instrument); ConfigManager.SetProperty(PropertyItem.Instrument, firstInstrument.ToString()); Instrument target = Instrument.Prefabs[firstInstrument]; bool soundEffects = !target.Name.StartsWith("Mordhau", true, System.Globalization.CultureInfo.InvariantCulture); ConfigManager.SetProperty(PropertyItem.SoundEffects, soundEffects.ToString()); ConfigManager.SetProperty(PropertyItem.LowestNoteId, target.LowestSentNote.ToString()); ConfigManager.SetProperty(PropertyItem.AvaliableNoteCount, target.NoteCount.ToString()); ConfigManager.SetProperty(PropertyItem.NoteCooldown, target.NoteCooldown.ToString()); ConfigManager.SetProperty(PropertyItem.LowestPlayedNote, target.LowestPlayedNote.ToString()); ConfigManager.SetProperty(PropertyItem.ForbidsChords, target.ForbidsChords.ToString()); tsm.UpdateTrackSelectionForInstrument(oldInstrument); player.mordhauOutDevice.UpdateNoteIdBounds(); } //} //else //{ // SaveManager.WriteSaveFile(SaveManager.SaveFilePath + namingForm.textBoxPartName.Text, trackConverter.GetPartitionToString()); // index.SaveIndex(); // PopulateIndexList(); // trackConverter = null; //} // Lastly, save the settings in a midi file with the same name, in the same folder, for ease of sharing... // TODO: Consider storing these in appdata, and providing a button to access them. Both might get complicated if I make partition playlists // Actually... I think I will store them in appdata. var midFileName = Path.Combine(partitionMidiPath, namingForm.textBoxPartName.Text + ".mid"); if (File.Exists(midFileName)) { File.Delete(midFileName); } Directory.CreateDirectory(partitionMidiPath); Task.Run(() => tsm.SaveTrackManager(midFileName)); // Lutebot doesn't need this anytime soon - and shouldn't offer the option to load it until it exists anyway } } } } else if (namingForm.DialogResult == DialogResult.Yes) { // They wanted to just add it as a track if (trackConverter == null) { trackConverter = new LuteMod.Converter.MordhauConverter(); } // These ranges and settings only matter for FillTrack. So re-setting them each time isn't a problem trackConverter.Range = ConfigManager.GetIntegerProperty(PropertyItem.AvaliableNoteCount); trackConverter.LowNote = ConfigManager.GetIntegerProperty(PropertyItem.LowestPlayedNote); trackConverter.IsConversionEnabled = true; trackConverter.SetDivision(player.sequence.Division); // This one could be weird trackConverter.AddTrack(); trackConverter.SetEnabledTracksInTrack(trackConverter.GetTrackCount() - 1, tsm.MidiTracks.Values.ToList()); trackConverter.SetEnabledMidiChannelsInTrack(trackConverter.GetTrackCount() - 1, tsm.MidiChannels.Values.ToList()); trackConverter.FillTrack(trackConverter.GetTrackCount() - 1, player.ExtractMidiContent()); } } else { MessageBox.Show("The partition is empty"); } }