Exemplo n.º 1
0
        public void CleanUpManifestFile()
        {
            // read file
            string        mp3ManifestFile = GetMp3ListFilePath();
            List <string> lines           = File.ReadAllText(mp3ManifestFile).Split(new[] { Environment.NewLine }, StringSplitOptions.None).ToList();
            string        pattern         = @"(.+) \| (.+)";

            string parseMp3(string line) => MatchGroup(line, pattern, 1);

            // filter out lines whose mp3s no longer exist
            List <string> keepLines = new List <string>();

            foreach (string line in lines)
            {
                var relMp3 = parseMp3(line);
                var absMp3 = JunUtils.FullPathFromSongsFolder(relMp3);
                if (File.Exists(absMp3))
                {
                    keepLines.Add(line);
                }
            }

            // write to file
            File.WriteAllText(mp3ManifestFile, String.Join(Environment.NewLine, keepLines));
        }
Exemplo n.º 2
0
        private void formAnimationTimer_Tick(object sender, EventArgs e)
        {
            Color startBackColor = Color.FromArgb(71, 115, 66);
            Color endBackColor   = Color.FromArgb(45, 42, 63);
            var   saveButtons    = new List <Button>()
            {
                saveButton1, saveButton2, saveButton3, saveButton4
            };

            for (int i = 0; i < saveButtons.Count; i++)
            {
                // linearly interpolate colour
                // saveButtonHighlight 1.0 -> 0.0
                if (saveButtonHighlight[i] > 0)
                {
                    saveButtons[i].FlatAppearance.MouseOverBackColor = JunUtils.LerpColor(startBackColor, endBackColor, 1 - saveButtonHighlight[i]);
                    saveButtons[i].ForeColor = JunUtils.LerpColor(Color.White, Color.FromArgb(90, 90, 134), 1 - saveButtonHighlight[i]);
                }

                // decay
                saveButtonHighlight[i] -= HIGHLIGHT_FADE;
                if (saveButtonHighlight[i] <= 0)
                {
                    saveButtonHighlight[i]   = 0M;
                    saveButtons[i].ForeColor = Color.FromArgb(90, 90, 134);
                    saveButtons[i].Text      = "Save";
                    saveButtons[i].FlatAppearance.MouseOverBackColor = Color.Empty;
                }
            }
        }
        // AR modifications: HR, DT, DTHR, BpmMultiplier
        public static decimal CalculateMultipliedAR(Beatmap map, decimal BpmMultiplier)
        {
            decimal newbpmMs = ApproachRateToMs(map.ApproachRate) / BpmMultiplier;
            decimal newbpmAR = MsToApproachRate(newbpmMs);

            return(JunUtils.Clamp(newbpmAR, 0, 11));
        }
Exemplo n.º 4
0
        public async void GenerateBeatmap()
        {
            if (State != EditorState.READY)
            {
                return;
            }

            // pre
            SetState(EditorState.GENERATING_BEATMAP);

            // main phase
            Beatmap exportBeatmap = NewBeatmap.Copy();

            ModifyBeatmapMetadata(exportBeatmap, BpmMultiplier, ChangePitch);
            if (!File.Exists(JunUtils.GetBeatmapDirectoryName(OriginalBeatmap) + "\\" + exportBeatmap.AudioFilename))
            {
                string inFile  = $"{Path.GetDirectoryName(OriginalBeatmap.Filename)}\\{OriginalBeatmap.AudioFilename}";
                string outFile = $"{Path.GetDirectoryName(exportBeatmap.Filename)}\\{exportBeatmap.AudioFilename}";
                await Task.Run(() => SongSpeedChanger.GenerateAudioFile(inFile, outFile, BpmMultiplier, ChangePitch));

                // take note of this mp3 in a text file, so we can clean it up later
                string mp3ManifestFile = Properties.Settings.Default.SongsFolder + "\\modified_mp3_list.txt";
                using (var writer = File.AppendText(mp3ManifestFile))
                {
                    string beatmapFolder   = Path.GetDirectoryName(exportBeatmap.Filename).Replace(Properties.Settings.Default.SongsFolder + "\\", "");
                    string mp3RelativePath = beatmapFolder + "\\" + exportBeatmap.AudioFilename;
                    writer.WriteLine(mp3RelativePath + " | " + exportBeatmap.Filename);
                }
            }
            exportBeatmap.Save();

            // post
            form.PlayDoneSound();
            SetState(EditorState.READY);
        }
Exemplo n.º 5
0
        public void GenerateBeatmap()
        {
            if (State != EditorState.READY)
            {
                return;
            }

            SetState(EditorState.GENERATING_BEATMAP);

            bool compensateForDT = (NewBeatmap.ApproachRate > 10 || NewBeatmap.OverallDifficulty > 10);

            // Set metadata
            Beatmap exportBeatmap = new Beatmap(NewBeatmap);

            ModifyBeatmapMetadata(exportBeatmap, BpmMultiplier, ChangePitch, compensateForDT);
            if (NoSpinners)
            {
                exportBeatmap.RemoveSpinners();
            }

            // Slow down map by 1.5x
            if (compensateForDT)
            {
                exportBeatmap.ApproachRate      = DifficultyCalculator.CalculateMultipliedAR(NewBeatmap, 1 / 1.5M);
                exportBeatmap.OverallDifficulty = DifficultyCalculator.CalculateMultipliedOD(NewBeatmap, 1 / 1.5M);
                decimal compensatedRate = (NewBeatmap.Bpm / OriginalBeatmap.Bpm) / 1.5M;
                exportBeatmap.SetRate(compensatedRate);
            }

            // Generate new mp3
            var audioFilePath = Path.Combine(JunUtils.GetBeatmapDirectoryName(OriginalBeatmap), exportBeatmap.AudioFilename);
            var newMp3        = "";

            if (!File.Exists(audioFilePath))
            {
                string inFile  = Path.Combine(Path.GetDirectoryName(OriginalBeatmap.Filename), OriginalBeatmap.AudioFilename);
                string outFile = Path.Combine(Path.GetTempPath(), exportBeatmap.AudioFilename);

                SongSpeedChanger.GenerateAudioFile(inFile, outFile, BpmMultiplier, mainform.BackgroundWorker, ChangePitch, compensateForDT);
                newMp3 = outFile;

                // take note of this mp3 in a text file, so we can clean it up later
                string        mp3ManifestFile = GetMp3ListFilePath();
                List <string> manifest        = File.ReadAllLines(mp3ManifestFile).ToList();
                string        beatmapFolder   = Path.GetDirectoryName(exportBeatmap.Filename).Replace(Properties.Settings.Default.SongsFolder + "\\", "");
                string        mp3RelativePath = Path.Combine(beatmapFolder, exportBeatmap.AudioFilename);
                manifest.Add(mp3RelativePath + " | " + exportBeatmap.Filename);
                File.WriteAllLines(mp3ManifestFile, manifest);
            }
            // save file to temp location (do not directly put into any song folder)
            exportBeatmap.Filename = Path.Combine(Path.GetTempPath(), Path.GetFileName(exportBeatmap.Filename));
            exportBeatmap.Save();

            // create and execute osz
            AddNewBeatmapToSongFolder(Path.GetDirectoryName(OriginalBeatmap.Filename), exportBeatmap.Filename, newMp3);

            // post
            SetState(EditorState.READY);
        }
Exemplo n.º 6
0
        public void SetBpm(int bpm)
        {
            float originalBpm   = GetOriginalBpmData().Item1;
            float newMultiplier = bpm / originalBpm;

            newMultiplier = JunUtils.Clamp(newMultiplier, 0.1f, 5.0f);
            SetBpmMultiplier(newMultiplier);
        }
Exemplo n.º 7
0
        public static float CalculateMultipliedOD(Beatmap map, float BpmMultiplier)
        {
            float newbpmMs = OverallDifficultyToMs(map.OverallDifficulty) / BpmMultiplier;
            float newbpmOD = MsToOverallDifficulty(newbpmMs);

            newbpmOD = (float)Math.Round(newbpmOD * 10.0f) / 10.0f;
            newbpmOD = JunUtils.Clamp(newbpmOD, 0, 10);
            return(newbpmOD);
        }
        public static decimal CalculateMultipliedOD(Beatmap map, decimal BpmMultiplier)
        {
            decimal newbpmMs = OverallDifficultyToMs(map.OverallDifficulty) / BpmMultiplier;
            decimal newbpmOD = MsToOverallDifficulty(newbpmMs);

            newbpmOD = (decimal)Math.Round(newbpmOD * 10.0M) / 10.0M;
            newbpmOD = JunUtils.Clamp(newbpmOD, 0, 11);
            return(newbpmOD);
        }
Exemplo n.º 9
0
        // return the new beatmap object if success
        // return null on failure
        private Beatmap LoadBeatmap(string beatmapPath)
        {
            // test if the beatmap is valid before committing to using it
            Beatmap retMap;

            try
            {
                retMap = new Beatmap(beatmapPath);
            }
            catch (FormatException e)
            {
                Console.WriteLine("Bad .osu file format");
                OriginalBeatmap = null;
                NewBeatmap      = null;
                return(null);
            }
            // Check if beatmap was loaded successfully
            if (retMap.Filename == null && retMap.Title == null)
            {
                Console.WriteLine("Bad .osu file format");
                return(null);
            }

            // Check if this map was generated by osu-trainer
            if (retMap.Tags.Contains("osutrainer"))
            {
                string[] diffFiles           = Directory.GetFiles(Path.GetDirectoryName(retMap.Filename), "*.osu");
                int      candidateSimilarity = int.MaxValue;
                Beatmap  candidate           = null;
                foreach (string diff in diffFiles)
                {
                    Beatmap map = new Beatmap(diff);
                    if (map.Tags.Contains("osutrainer"))
                    {
                        continue;
                    }
                    // lower value => more similar
                    int similarity = JunUtils.LevenshteinDistance(retMap.Version, map.Version);
                    if (similarity < candidateSimilarity)
                    {
                        candidate           = map;
                        candidateSimilarity = similarity;
                    }
                }
                // just assume this shit is the original beatmap
                if (candidate != null)
                {
                    retMap = candidate;
                }
            }

            return(retMap);
        }
Exemplo n.º 10
0
        public DeleteMp3sForm(List <string> filesToDelete)
        {
            InitializeComponent();
            List <string> uniqueFilesToDelete = filesToDelete.Distinct().ToList();

            // filter out files that don't exist
            Dictionary <string, string> pathMapping = new Dictionary <string, string>();

            uniqueFilesToDelete.ForEach(file => pathMapping.Add(file, JunUtils.FullPathFromSongsFolder(file)));
            List <string> relativeFileList = uniqueFilesToDelete.Where(file => File.Exists(JunUtils.FullPathFromSongsFolder(file))).ToList();

            // populate listview
            string formatFileSize(long len)
            {
                string[] sizes = { "B", "KB", "MB", "GB", "TB" };
                int      order = 0;
                decimal  value = (decimal)len;

                while (value >= 1024 && order < sizes.Length - 1)
                {
                    order++;
                    value = value / 1024;
                }
                return(String.Format("{0:0.##} {1}", value, sizes[order]));
            }

            ListViewItem fileToListViewItem(string file)
            {
                string[] subitems = new string[3];
                FileInfo fi       = new FileInfo(JunUtils.FullPathFromSongsFolder(file));

                subitems[0] = file;                          // path relative to songs folder
                subitems[1] = fi.CreationTime.ToString("d"); // date created
                subitems[2] = formatFileSize(fi.Length);     // size
                return(new ListViewItem(subitems));
            }

            fileListView.Items.Clear();
            relativeFileList
            .Select(file => fileToListViewItem(file))
            .ToList()
            .ForEach(item => fileListView.Items.Add(item));

            // update total filesize label
            long totalSize = relativeFileList
                             .Select(file => new FileInfo(JunUtils.FullPathFromSongsFolder(file)).Length)
                             .Sum();

            fileSizeLabel.Text = $"Total: {formatFileSize(totalSize)} to be deleted";

            confirmButton.Focus();
        }
Exemplo n.º 11
0
        public static Color LerpColor(Color StartColor, Color EndColor, decimal Progress)
        {
            var startSat   = StartColor.GetSaturation();
            var startHue   = StartColor.GetHue();
            var startBri   = StartColor.GetBrightness();
            var endSat     = EndColor.GetSaturation();
            var endHue     = EndColor.GetHue();
            var endBri     = EndColor.GetBrightness();
            var currentSat = startSat + (float)Progress * (endSat - startSat);
            var currentHue = startHue + (float)Progress * (endHue - startHue);
            var currentBri = startBri + (float)Progress * (endBri - startBri);

            return(JunUtils.ColorFromHSV(currentHue, currentSat, currentBri));
        }
Exemplo n.º 12
0
        public List <string> GetUnusedMp3s()
        {
            // read manifest file
            List <string> lines           = new List <string>();
            string        mp3ManifestFile = GetMp3ListFilePath();

            if (!File.Exists(mp3ManifestFile))
            {
                return(new List <string>());
            }

            using (var reader = File.OpenText(mp3ManifestFile))
            {
                string line = "";
                while ((line = reader.ReadLine()) != null)
                {
                    lines.Add(line);
                }
            }

            // convert that shit into a dictionary
            var    mp3Dict = new Dictionary <string, List <string> >();
            string pattern = @"(.+) \| (.+)";

            string parseMp3(string line) => MatchGroup(line, pattern, 1);
            string parseOsu(string line) => MatchGroup(line, pattern, 2);

            // create dictionary keys
            lines
            .Select(line => parseMp3(line)).ToList()
            .Distinct()
            .ToList()
            .ForEach(mp3 => mp3Dict.Add(mp3, new List <string>()));

            // populate dictionary values
            foreach ((string mp3, string osu) in lines.Select(line => (parseMp3(line), parseOsu(line))))
            {
                mp3Dict[mp3].Add(osu);
            }

            // find all keys where none of the associated beatmaps exist, but the mp3 still exists
            bool noFilesExist(bool acc, string file) => acc && !File.Exists(file);

            return(lines
                   .Select(line => parseMp3(line))
                   .Where(mp3 => mp3Dict[mp3].Aggregate(true, noFilesExist))
                   .Where(mp3 => File.Exists(JunUtils.FullPathFromSongsFolder(mp3)))
                   .ToList());
        }
Exemplo n.º 13
0
        private void DeleteButton_Click(object sender, EventArgs e)
        {
            var mp3List = editor.GetUnusedMp3s();

            if (new DeleteMp3sForm(mp3List).ShowDialog() == DialogResult.OK)
            {
                mp3List
                .Select(relativeMp3 => JunUtils.FullPathFromSongsFolder(relativeMp3))
                .Select(absMp3 => new FileInfo(absMp3))
                .ToList()
                .ForEach(file => file.Delete());
                if (mp3List.Count > 0)
                {
                    MessageBox.Show($"Deleted {mp3List.Count} file(s).", "Success");
                }
                editor.CleanUpManifestFile();
            }
        }
Exemplo n.º 14
0
        private void DeleteButton_Click(object sender, EventArgs e)
        {
            if (gameLoaded == true)
            {
                MessageBox.Show("Please close osu! first then try again.", "osu! is running");
                return;
            }
            var mp3List = editor.GetUnusedMp3s();

            if (new DeleteMp3sForm(mp3List).ShowDialog() == DialogResult.OK)
            {
                mp3List
                .Select(relativeMp3 => JunUtils.FullPathFromSongsFolder(relativeMp3))
                .Select(absMp3 => new FileInfo(absMp3))
                .ToList()
                .ForEach(file => file.Delete());
                if (mp3List.Count > 0)
                {
                    MessageBox.Show($"Deleted {mp3List.Count} file(s).", "Success");
                }
                editor.CleanUpManifestFile();
            }
        }
Exemplo n.º 15
0
        public static void GenerateAudioFile(string inFile, string outFile, decimal multiplier, BackgroundWorker worker, bool changePitch = false, bool preDT = false)
        {
            decimal compensatedMultiplier = multiplier / 1.5M;

            string temp1 = JunUtils.GetTempFilename("mp3"); // audio copy
            string temp2 = JunUtils.GetTempFilename("wav"); // decoded wav
            string temp3 = JunUtils.GetTempFilename("wav"); // stretched file
            string temp4 = JunUtils.GetTempFilename("mp3"); // encoded mp3

            // TODO: try catch
            File.Copy(inFile, temp1);

            worker.ReportProgress(17);

            // mp3 => wav
            Process lame1 = new Process();

            lame1.StartInfo.FileName        = Path.Combine("Speed Changer Stuff", "lame.exe");
            lame1.StartInfo.Arguments       = $"-q 9 --priority 4 --decode \"{temp1}\" \"{temp2}\"";
            lame1.StartInfo.UseShellExecute = false;
            lame1.StartInfo.CreateNoWindow  = true;
            lame1.Start();
            lame1.WaitForExit();

            worker.ReportProgress(33);

            // stretch (or speed up) wav
            decimal selectedMultiplier = (preDT ? compensatedMultiplier : multiplier);
            decimal tempo = (selectedMultiplier - 1) * 100;

            decimal cents        = (decimal)(1200.0 * Math.Log((double)multiplier) / Math.Log(2));
            decimal semitones    = cents / 100.0M;
            Process soundstretch = new Process();

            soundstretch.StartInfo.FileName = Path.Combine("Speed Changer Stuff", "soundstretch.exe");
            if (changePitch)
            {
                soundstretch.StartInfo.Arguments = $"\"{temp2}\" \"{temp3}\" -quick -naa -tempo={tempo} -pitch={semitones}";
            }
            else
            {
                soundstretch.StartInfo.Arguments = $"\"{temp2}\" \"{temp3}\" -quick -naa -tempo={tempo}";
            }
            Console.WriteLine(soundstretch.StartInfo.Arguments);
            soundstretch.StartInfo.UseShellExecute = false;
            soundstretch.StartInfo.CreateNoWindow  = true;
            soundstretch.Start();
            soundstretch.WaitForExit();

            worker.ReportProgress(50);

            // wav => mp3
            Process lame2 = new Process();

            lame2.StartInfo.FileName        = Path.Combine("Speed Changer Stuff", "lame.exe");
            lame2.StartInfo.Arguments       = $"-q 9 --priority 4 \"{temp3}\" \"{temp4}\"";
            lame2.StartInfo.UseShellExecute = false;
            lame2.StartInfo.CreateNoWindow  = true;
            lame2.Start();
            lame2.WaitForExit();

            worker.ReportProgress(67);

            if (File.Exists(outFile))
            {
                File.Delete(outFile);
            }
            File.Copy(temp4, outFile);
            worker.ReportProgress(83);

            // Clean up
            File.Delete(temp1);
            File.Delete(temp2);
            File.Delete(temp3);
            File.Delete(temp4);
            worker.ReportProgress(100);
        }
Exemplo n.º 16
0
        // OUT: beatmap.Version
        // OUT: beatmap.Filename
        // OUT: beatmap.AudioFilename (if multiplier is not 1x)
        // OUT: beatmap.Tags
        private void ModifyBeatmapMetadata(Beatmap map, float multiplier, bool changePitch = false)
        {
            if (multiplier == 1)
            {
                string ARODCS = "";
                if (NewBeatmap.ApproachRate != OriginalBeatmap.ApproachRate)
                {
                    ARODCS += $" AR{NewBeatmap.ApproachRate}";
                }
                if (NewBeatmap.OverallDifficulty != OriginalBeatmap.OverallDifficulty)
                {
                    ARODCS += $" OD{NewBeatmap.OverallDifficulty}";
                }
                if (NewBeatmap.CircleSize != OriginalBeatmap.CircleSize)
                {
                    ARODCS += $" CS{NewBeatmap.CircleSize}";
                }
                map.Version += ARODCS;
            }
            else
            {
                // If song has changed, no ARODCS in diff name
                var bpmsUnique = GetBpmList(map).Distinct().ToList();
                if (bpmsUnique.Count >= 2)
                {
                    map.Version += $" x{multiplier}";
                }
                else
                {
                    map.Version += $" {(bpmsUnique[0]).ToString("0")}bpm";
                }
                map.AudioFilename = map.AudioFilename.Substring(0, map.AudioFilename.LastIndexOf(".", StringComparison.InvariantCulture)) + " " + GetBpmData(map).Item1 + "bpm.mp3";
                if (changePitch)
                {
                    map.AudioFilename = $"{Path.GetFileNameWithoutExtension(map.AudioFilename)} {multiplier:0.000}x.mp3";
                }
                if (changePitch)
                {
                    map.AudioFilename = $"{Path.GetFileNameWithoutExtension(map.AudioFilename)} {multiplier:0.000}x (pitch {(multiplier < 1 ? "lowered" : "raised")}).mp3";
                }
            }

            map.Filename = map.Filename.Substring(0, map.Filename.LastIndexOf("\\", StringComparison.InvariantCulture) + 1) + JunUtils.NormalizeText(map.Artist) + " - " + JunUtils.NormalizeText(map.Title) + " (" + JunUtils.NormalizeText(map.Creator) + ")" + " [" + JunUtils.NormalizeText(map.Version) + "].osu";
            // make this map searchable in the in-game menus
            map.Tags.Add("osutrainer");
        }
Exemplo n.º 17
0
        private async void ServiceBeatmapChangeRequest()
        {
            // acquire mutually exclusive entry into this method
            serviceBeatmapRequestLocked = true;

            Beatmap candidateOriginalBeatmap = null, candidateNewBeatmap = null;

            while (completedBeatmapRequest == null || completedBeatmapRequest.RequestNumber != mapChangeRequests.Last().RequestNumber)
            {
                completedBeatmapRequest  = mapChangeRequests.Last();
                candidateOriginalBeatmap = await Task.Run(() => LoadBeatmap(mapChangeRequests.Last().Name));

                if (candidateOriginalBeatmap != null)
                {
                    candidateNewBeatmap = new Beatmap(candidateOriginalBeatmap);
                }

                // if a new request came in, invalidate candidate beatmap and service the new request
            }

            // no new requests, we can commit to using this beatmap
            OriginalBeatmap = candidateOriginalBeatmap;
            NewBeatmap      = candidateNewBeatmap;
            if (OriginalBeatmap == null)
            {
                SetState(EditorState.NOT_READY);
                NotReadyReason = BadBeatmapReason.ERROR_LOADING_BEATMAP;
                BeatmapSwitched?.Invoke(this, EventArgs.Empty);
            }
            else
            {
                if (OriginalBeatmap.HitObjectCount == 0)
                {
                    SetState(EditorState.NOT_READY);
                    NotReadyReason = BadBeatmapReason.EMPTY_MAP;
                    BeatmapSwitched?.Invoke(this, EventArgs.Empty);
                }
                else
                {
                    // Apply multiplier
                    NewBeatmap.SetRate(BpmMultiplier);

                    // Apply bpm scaled settings
                    if (ScaleAR)
                    {
                        NewBeatmap.ApproachRate = DifficultyCalculator.CalculateMultipliedAR(candidateOriginalBeatmap, BpmMultiplier);
                    }
                    if (ScaleOD)
                    {
                        NewBeatmap.OverallDifficulty = DifficultyCalculator.CalculateMultipliedOD(candidateOriginalBeatmap, BpmMultiplier);
                    }

                    // Apply locked settings
                    if (HpIsLocked)
                    {
                        NewBeatmap.HPDrainRate = lockedHP;
                    }
                    if (CsIsLocked)
                    {
                        NewBeatmap.CircleSize = lockedCS;
                    }
                    if (ArIsLocked)
                    {
                        NewBeatmap.ApproachRate = lockedAR;
                    }
                    if (OdIsLocked)
                    {
                        NewBeatmap.OverallDifficulty = lockedOD;
                    }
                    if (BpmIsLocked)
                    {
                        SetBpm(lockedBpm);
                    }

                    // Apply Hardrock
                    if (EmulateHardrock)
                    {
                        NewBeatmap.CircleSize = OriginalBeatmap.CircleSize * 1.3M;
                    }
                    if (EmulateHardrock)
                    {
                        NewBeatmap.OverallDifficulty = JunUtils.Clamp(GetScaledOD() * 1.4M, 0M, 10M);
                    }

                    SetState(EditorState.READY);
                    RequestDiffCalc();
                    BeatmapSwitched?.Invoke(this, EventArgs.Empty);
                    BeatmapModified?.Invoke(this, EventArgs.Empty);
                }
            }
            ControlsModified?.Invoke(this, EventArgs.Empty);
            serviceBeatmapRequestLocked = false;
        }
Exemplo n.º 18
0
        public static void GenerateAudioFile(string inFile, string outFile, double multiplier, bool changePitch = false)
        {
            if (multiplier == 1)
            {
                throw new ArgumentException("Don't call this function if multiplier is 1.0x");
            }

            string temp1 = JunUtils.GetTempFilename("mp3"); // audio copy
            string temp2 = JunUtils.GetTempFilename("wav"); // decoded wav
            string temp3 = JunUtils.GetTempFilename("wav"); // stretched file
            string temp4 = JunUtils.GetTempFilename("mp3"); // encoded mp3

            // TODO: try catch
            CopyFile(inFile, temp1);

            // mp3 => wav
            Process lame1 = new Process();

            lame1.StartInfo.FileName        = "Speed Changer Stuff\\lame.exe";
            lame1.StartInfo.Arguments       = $"-q 9 --priority 4 --decode \"{temp1}\" \"{temp2}\"";
            lame1.StartInfo.UseShellExecute = false;
            lame1.StartInfo.CreateNoWindow  = true;
            lame1.Start();
            lame1.WaitForExit();

            // stretch (or speed up) wav
            float   cents        = (float)(1200.0f * Math.Log(multiplier) / Math.Log(2));
            float   semitones    = cents / 100.0f;
            Process soundstretch = new Process();

            soundstretch.StartInfo.FileName = "Speed Changer Stuff\\soundstretch.exe";
            if (changePitch)
            {
                soundstretch.StartInfo.Arguments = $"\"{temp2}\" \"{temp3}\" -quick -naa -tempo={(multiplier - 1) * 100} -pitch={semitones}";
            }
            else
            {
                soundstretch.StartInfo.Arguments = $"\"{temp2}\" \"{temp3}\" -quick -naa -tempo={(multiplier - 1) * 100}";
            }
            soundstretch.StartInfo.UseShellExecute = false;
            soundstretch.StartInfo.CreateNoWindow  = true;
            soundstretch.Start();
            soundstretch.WaitForExit();

            // wav => mp3
            Process lame2 = new Process();

            lame2.StartInfo.FileName        = "Speed Changer Stuff\\lame.exe";
            lame2.StartInfo.Arguments       = $"-q 9 --priority 4 \"{temp3}\" \"{temp4}\"";
            lame2.StartInfo.UseShellExecute = false;
            lame2.StartInfo.CreateNoWindow  = true;
            lame2.Start();
            lame2.WaitForExit();

            CopyFile(temp4, outFile);

            // Clean up
            File.Delete(temp1);
            File.Delete(temp2);
            File.Delete(temp3);
            File.Delete(temp4);
        }