public static bool ExportSample(SampleGeneratingArgs sampleGeneratingArgs, string name, string exportFolder, Dictionary <SampleGeneratingArgs, SampleSoundGenerator> loadedSamples = null) { SampleSoundGenerator sampleSoundGenerator; if (loadedSamples != null) { if (SampleImporter.ValidateSampleArgs(sampleGeneratingArgs, loadedSamples)) { sampleSoundGenerator = loadedSamples[sampleGeneratingArgs]; } else { return(false); } } else { try { sampleSoundGenerator = SampleImporter.ImportSample(sampleGeneratingArgs); } catch (Exception ex) { Console.WriteLine($@"{ex.Message} while importing sample {sampleGeneratingArgs}."); return(false); } } // TODO: Allow mp3, ogg and aif export. string filename = name + ".wav"; CreateWaveFile(Path.Combine(exportFolder, filename), sampleSoundGenerator.GetSampleProvider().ToWaveProvider16()); return(true); }
public static Dictionary <SampleGeneratingArgs, string> GenerateSampleNames(IEnumerable <SampleGeneratingArgs> samples, Dictionary <SampleGeneratingArgs, SampleSoundGenerator> loadedSamples) { var usedNames = new HashSet <string>(); var sampleNames = new Dictionary <SampleGeneratingArgs, string>(); foreach (var sample in samples) { if (!SampleImporter.ValidateSampleArgs(sample, loadedSamples)) { sampleNames[sample] = string.Empty; continue; } var baseName = sample.GetFilename(); var name = baseName; int i = 1; while (usedNames.Contains(name)) { name = baseName + "-" + ++i; } usedNames.Add(name); sampleNames[sample] = name; } return(sampleNames); }
public static void ExportCustomIndices(List<CustomIndex> customIndices, string exportFolder, Dictionary<SampleGeneratingArgs, SampleSoundGenerator> loadedSamples=null) { foreach (CustomIndex ci in customIndices) { foreach (KeyValuePair<string, HashSet<SampleGeneratingArgs>> kvp in ci.Samples) { if (kvp.Value.Count == 0) { continue; } var samples = new List<ISampleProvider>(); var volumes = new List<double>(); int soundsAdded = 0; if (loadedSamples != null) { foreach (SampleGeneratingArgs args in kvp.Value) { if (SampleImporter.ValidateSampleArgs(args, loadedSamples)) { var sample = loadedSamples[args]; samples.Add(sample.GetSampleProvider()); volumes.Add(sample.VolumeCorrection != -1 ? sample.VolumeCorrection : 1f); soundsAdded++; } } } else { foreach (SampleGeneratingArgs args in kvp.Value) { try { var sample = SampleImporter.ImportSample(args); samples.Add(sample.GetSampleProvider()); volumes.Add(sample.VolumeCorrection != -1 ? sample.VolumeCorrection : 1f); soundsAdded++; } catch (Exception) { } } } if (soundsAdded == 0) { continue; } int maxSampleRate = samples.Max(o => o.WaveFormat.SampleRate); int maxChannels = samples.Max(o => o.WaveFormat.Channels); IEnumerable<ISampleProvider> sameFormatSamples = samples.Select(o => (ISampleProvider)new WdlResamplingSampleProvider(SampleImporter.SetChannels(o, maxChannels), maxSampleRate)); ISampleProvider result = new MixingSampleProvider(sameFormatSamples); if (soundsAdded > 1) { result = new VolumeSampleProvider(result) { Volume = (float)(1 / Math.Sqrt(soundsAdded * volumes.Average())) }; result = new SimpleCompressorEffect(result) { Threshold = 16, Ratio = 6, Attack = 0.1, Release = 0.1, Enabled = true, MakeUpGain = 15 * Math.Log10(Math.Sqrt(soundsAdded * volumes.Average())) }; } // TODO: Allow mp3, ogg and aif export. string filename = ci.Index == 1 ? kvp.Key + ".wav" : kvp.Key + ci.Index + ".wav"; CreateWaveFile(Path.Combine(exportFolder, filename), result.ToWaveProvider16()); } } }
public static void BalanceVolumes(List <SamplePackage> packages, VolumeBalancingArgs args) { foreach (SamplePackage package in packages) { double maxVolume = package.Samples.Max(o => o.SampleArgs.Volume); if (Math.Abs(maxVolume - -0.01) < Precision.DOUBLE_EPSILON) { maxVolume = 1; } foreach (Sample sample in package.Samples) { if (Math.Abs(sample.SampleArgs.Volume - -0.01) < Precision.DOUBLE_EPSILON) { sample.SampleArgs.Volume = 1; } // I pick the new volume such that the samples have a volume as high as possible and the greenline brings the volume down. // With this equation the final amplitude stays the same while the greenline has the volume of the loudest sample at this time. double newVolume = SampleImporter.AmplitudeToVolume( SampleImporter.VolumeToAmplitude(package.Volume) * SampleImporter.VolumeToAmplitude(sample.SampleArgs.Volume) / SampleImporter.VolumeToAmplitude(maxVolume)); if (Math.Abs(newVolume - 1) > args.Roughness && !args.AlwaysFullVolume) { // If roughness is not 0 it will quantize the new volume in order to reduce the number of different volumes sample.SampleArgs.Volume = Math.Abs(args.Roughness) > Precision.DOUBLE_EPSILON ? args.Roughness * Math.Round(newVolume / args.Roughness) : newVolume; } else { sample.SampleArgs.Volume = 1; } } if (args.AlwaysFullVolume) { // Assuming the volume of the sample is always maximum, this equation makes sure that // the loudest sample at this time has the wanted amplitude using the volume change from the greenline. package.Volume = SampleImporter.AmplitudeToVolume( SampleImporter.VolumeToAmplitude(package.Volume) * SampleImporter.VolumeToAmplitude(maxVolume)); } else { package.Volume = maxVolume; } } }
public static void ExportLoadedSamples(Dictionary <SampleGeneratingArgs, SampleSoundGenerator> loadedSamples, string exportFolder, Dictionary <SampleGeneratingArgs, string> names = null, SampleExportFormat format = SampleExportFormat.Default) { if (names == null) { names = GenerateSampleNames(loadedSamples.Keys, loadedSamples); } foreach (var sample in loadedSamples.Keys.Where(sample => SampleImporter.ValidateSampleArgs(sample, loadedSamples))) { ExportSample(sample, names[sample], exportFolder, loadedSamples, format); } }
/// <summary> /// /// </summary> /// <param name="loadedSamples"></param> public void CleanInvalids(Dictionary <SampleGeneratingArgs, SampleSoundGenerator> loadedSamples = null, bool validateSampleFile = true) { // Replace all invalid paths with "" and remove the invalid path if another valid path is also in the hashset foreach (HashSet <SampleGeneratingArgs> paths in Samples.Values) { int initialCount = paths.Count; int removed = paths.RemoveWhere(o => !SampleImporter.ValidateSampleArgs(o, loadedSamples, validateSampleFile)); if (paths.Count == 0 && initialCount != 0) { // All the paths where invalid and it didn't just start out empty paths.Add(new SampleGeneratingArgs()); // This "" is here to prevent this hashset from getting new paths } } }
public static void AddNewSampleName(Dictionary <SampleGeneratingArgs, string> sampleNames, SampleGeneratingArgs sample, Dictionary <SampleGeneratingArgs, SampleSoundGenerator> loadedSamples) { if (!SampleImporter.ValidateSampleArgs(sample, loadedSamples)) { sampleNames[sample] = string.Empty; return; } var baseName = sample.GetFilename(); var name = baseName; int i = 1; while (sampleNames.ContainsValue(name)) { name = baseName + "-" + ++i; } sampleNames[sample] = name; }
/// <summary> /// /// </summary> /// <returns></returns> public ISampleProvider GetSampleProvider() { Wave.Position = 0; ISampleProvider output = WaveToSampleProvider(Wave); if (FadeStart != -1 && FadeLength != -1) { output = new DelayFadeOutSampleProvider(output); ((DelayFadeOutSampleProvider)output).BeginFadeOut(FadeStart * 1000, FadeLength * 1000); } if (KeyCorrection != 0) { output = SampleImporter.PitchShift(output, KeyCorrection); } if (VolumeCorrection != 1) { output = SampleImporter.VolumeChange(output, VolumeCorrection); } return(output); }
public static List <HitsoundEvent> GetHitsounds(List <SamplePackage> samplePackages, ref Dictionary <SampleGeneratingArgs, SampleSoundGenerator> loadedSamples, ref Dictionary <SampleGeneratingArgs, string> names, ref Dictionary <SampleGeneratingArgs, Vector2> positions) { HashSet <SampleGeneratingArgs> allSampleArgs = new HashSet <SampleGeneratingArgs>(); foreach (SamplePackage sp in samplePackages) { allSampleArgs.UnionWith(sp.Samples.Select(o => o.SampleArgs)); } if (loadedSamples == null) { loadedSamples = SampleImporter.ImportSamples(allSampleArgs); } if (names == null) { names = HitsoundExporter.GenerateSampleNames(allSampleArgs, loadedSamples); } if (positions == null) { positions = HitsoundExporter.GenerateHitsoundPositions(allSampleArgs); } var hitsounds = new List <HitsoundEvent>(); foreach (var p in samplePackages) { foreach (var s in p.Samples) { hitsounds.Add(new HitsoundEvent(p.Time, positions[s.SampleArgs], s.OutsideVolume, names[s.SampleArgs], s.SampleSet, s.SampleSet, 0, s.Whistle, s.Finish, s.Clap)); } } return(hitsounds); }
public static void ExportMixedSample(IEnumerable <SampleGeneratingArgs> sampleGeneratingArgses, string name, string exportFolder, Dictionary <SampleGeneratingArgs, SampleSoundGenerator> loadedSamples = null, SampleExportFormat format = SampleExportFormat.Default, SampleExportFormat mixedFormat = SampleExportFormat.Default) { // Try loading all the valid samples var validLoadedSamples = new Dictionary <SampleGeneratingArgs, SampleSoundGenerator>(); if (loadedSamples != null) { foreach (var args in sampleGeneratingArgses) { if (!SampleImporter.ValidateSampleArgs(args, loadedSamples)) { continue; } var sample = loadedSamples[args]; validLoadedSamples.Add(args, sample); } } else { // Import each sample individually foreach (SampleGeneratingArgs args in sampleGeneratingArgses) { try { var sample = SampleImporter.ImportSample(args); validLoadedSamples.Add(args, sample); } catch (Exception ex) { Console.WriteLine($@"{ex.Message} while importing sample {args}."); } } } if (validLoadedSamples.Count == 0) { return; } // If all the valid samples are blank samples, then also export only a single blank sample if (validLoadedSamples.Count == 1 || validLoadedSamples.All(o => o.Value.BlankSample)) { // It has only one valid sample, so we can just export it with the single sample export ExportSample(validLoadedSamples.Keys.First(), name, exportFolder, loadedSamples, format); } else if (validLoadedSamples.Count > 1) { // Synchronize the sample rate and channels for all samples and get the sample providers int maxSampleRate = validLoadedSamples.Values.Max(o => o.Wave.WaveFormat.SampleRate); int maxChannels = validLoadedSamples.Values.Max(o => o.Wave.WaveFormat.Channels); IEnumerable <ISampleProvider> sameFormatSamples = validLoadedSamples.Select(o => (ISampleProvider) new WdlResamplingSampleProvider(SampleImporter.SetChannels(o.Value.GetSampleProvider(), maxChannels), maxSampleRate)); ISampleProvider sampleProvider = new MixingSampleProvider(sameFormatSamples); // If the input is Ieee float or you are mixing multiple samples, then clipping is possible, // so you can either export as IEEE float or use a compressor and export as 16-bit PCM (half filesize) or Vorbis (even smaller filesize) // If the input is only The Blank Sample then it should export The Blank Sample if (mixedFormat == SampleExportFormat.WavePcm || mixedFormat == SampleExportFormat.OggVorbis) { // When the sample is mixed and the export format is PCM or Vorbis, then clipping is possible, so we add a limiter sampleProvider = new SoftLimiter(sampleProvider); } switch (mixedFormat) { case SampleExportFormat.WaveIeeeFloat: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider()); break; case SampleExportFormat.WavePcm: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider16()); break; case SampleExportFormat.OggVorbis: VorbisFileWriter.CreateVorbisFile(Path.Combine(exportFolder, name + ".ogg"), sampleProvider.ToWaveProvider()); break; default: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider()); break; } } }
public static bool ExportSample(SampleGeneratingArgs sampleGeneratingArgs, string name, string exportFolder, Dictionary <SampleGeneratingArgs, SampleSoundGenerator> loadedSamples = null, SampleExportFormat format = SampleExportFormat.Default) { if (sampleGeneratingArgs.CanCopyPaste && format == SampleExportFormat.Default) { var dest = Path.Combine(exportFolder, name + sampleGeneratingArgs.GetExtension()); return(CopySample(sampleGeneratingArgs.Path, dest)); } SampleSoundGenerator sampleSoundGenerator; if (loadedSamples != null) { if (SampleImporter.ValidateSampleArgs(sampleGeneratingArgs, loadedSamples)) { sampleSoundGenerator = loadedSamples[sampleGeneratingArgs]; } else { return(false); } } else { try { sampleSoundGenerator = SampleImporter.ImportSample(sampleGeneratingArgs); } catch (Exception ex) { Console.WriteLine($@"{ex.Message} while importing sample {sampleGeneratingArgs}."); return(false); } } var sourceEncoding = sampleSoundGenerator.Wave.WaveFormat.Encoding; // Either if it is the blank sample or the source file is literally what the user wants to be exported if (sampleSoundGenerator.BlankSample && sampleGeneratingArgs.GetExtension() == ".wav" || sampleGeneratingArgs.CanCopyPaste && IsFormatEncodingCompatible(sourceEncoding, format)) { var dest = Path.Combine(exportFolder, name + sampleGeneratingArgs.GetExtension()); return(CopySample(sampleGeneratingArgs.Path, dest)); } var sampleProvider = sampleSoundGenerator.GetSampleProvider(); if ((format == SampleExportFormat.WavePcm || format == SampleExportFormat.OggVorbis) && sourceEncoding == WaveFormatEncoding.IeeeFloat) { // When the source is IEEE float and the export format is PCM or Vorbis, then clipping is possible, so we add a limiter sampleProvider = new SoftLimiter(sampleProvider); } switch (format) { case SampleExportFormat.WaveIeeeFloat: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider()); break; case SampleExportFormat.WavePcm: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider16()); break; case SampleExportFormat.OggVorbis: VorbisFileWriter.CreateVorbisFile(Path.Combine(exportFolder, name + ".ogg"), sampleProvider.ToWaveProvider()); break; default: switch (sourceEncoding) { case WaveFormatEncoding.IeeeFloat: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider()); break; case WaveFormatEncoding.Pcm: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider16()); break; case WaveFormatEncoding.Vorbis1: case WaveFormatEncoding.Vorbis2: case WaveFormatEncoding.Vorbis3: case WaveFormatEncoding.Vorbis1P: case WaveFormatEncoding.Vorbis2P: case WaveFormatEncoding.Vorbis3P: // Vorbis files default to being exported as 16-bit PCM wave files because that's lossless CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider16()); break; default: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider()); break; } break; } return(true); }
/// <summary> /// Balances the volume of <see cref="SamplePackage"/> such that volume is mostly handled by osu!'s volume controllers rather than /// in-sample amplitude changes. /// </summary> /// <param name="packages"></param> /// <param name="roughness">Quantizing level in the new volumes of samples. Can be used to decrease the number of distinct volume levels.</param> /// <param name="alwaysFullVolume">Forces to always use maximum amplitude in the samples.</param> /// <param name="individualVolume">Allows for multiple distinct volume levels within a single <see cref="SamplePackage"/>.</param> public static void BalanceVolumes(IEnumerable <SamplePackage> packages, double roughness, bool alwaysFullVolume, bool individualVolume = false) { foreach (SamplePackage package in packages) { if (individualVolume) { // Simply mix the volume in the sample to the outside volume foreach (Sample sample in package.Samples) { sample.OutsideVolume = SampleImporter.AmplitudeToVolume( SampleImporter.VolumeToAmplitude(sample.OutsideVolume) * SampleImporter.VolumeToAmplitude(sample.SampleArgs.Volume)); sample.SampleArgs.Volume = 1; } continue; } double maxVolume = package.Samples.Max(o => o.SampleArgs.Volume); if (Math.Abs(maxVolume - -0.01) < Precision.DOUBLE_EPSILON) { maxVolume = 1; } foreach (Sample sample in package.Samples) { if (Math.Abs(sample.SampleArgs.Volume - -0.01) < Precision.DOUBLE_EPSILON) { sample.SampleArgs.Volume = 1; } // Pick the new volume such that the samples have a volume as high as possible and the greenline brings the volume down. // With this equation the final amplitude stays the same while the greenline has the volume of the loudest sample at this time. double newVolume = SampleImporter.AmplitudeToVolume( SampleImporter.VolumeToAmplitude(sample.OutsideVolume) * SampleImporter.VolumeToAmplitude(sample.SampleArgs.Volume) / SampleImporter.VolumeToAmplitude(maxVolume)); if (Math.Abs(newVolume - 1) > roughness && !alwaysFullVolume) { // If roughness is not 0 it will quantize the new volume in order to reduce the number of different volumes sample.SampleArgs.Volume = Math.Abs(roughness) > Precision.DOUBLE_EPSILON ? roughness * Math.Round(newVolume / roughness) : newVolume; } else { sample.SampleArgs.Volume = 1; } } if (alwaysFullVolume) { // Assuming the volume of the sample is always maximum, this equation makes sure that // the loudest sample at this time has the wanted amplitude using the volume change from the greenline. package.SetAllOutsideVolume(SampleImporter.AmplitudeToVolume( SampleImporter.VolumeToAmplitude(package.MaxOutsideVolume) * SampleImporter.VolumeToAmplitude(maxVolume))); } else { package.SetAllOutsideVolume(maxVolume); } } }
public static List <HitsoundEvent> GetHitsounds(List <SamplePackage> samplePackages, ref Dictionary <SampleGeneratingArgs, SampleSoundGenerator> loadedSamples, ref Dictionary <SampleGeneratingArgs, string> names, ref Dictionary <SampleGeneratingArgs, Vector2> positions, bool maniaPositions = false, bool includeRegularHitsounds = true, bool allowNamingGrowth = false, bool validateSampleFile = true, SampleGeneratingArgsComparer comparer = null) { if (comparer == null) { comparer = new SampleGeneratingArgsComparer(); } HashSet <SampleGeneratingArgs> allSampleArgs = new HashSet <SampleGeneratingArgs>(comparer); foreach (SamplePackage sp in samplePackages) { allSampleArgs.UnionWith(sp.Samples.Select(o => o.SampleArgs)); } if (loadedSamples == null) { loadedSamples = SampleImporter.ImportSamples(allSampleArgs, comparer); } if (names == null) { names = HitsoundExporter.GenerateSampleNames(allSampleArgs, loadedSamples, validateSampleFile, comparer); } if (positions == null) { positions = maniaPositions ? HitsoundExporter.GenerateManiaHitsoundPositions(allSampleArgs, comparer) : HitsoundExporter.GenerateHitsoundPositions(allSampleArgs, comparer); } var hitsounds = new List <HitsoundEvent>(); foreach (var p in samplePackages) { foreach (var s in p.Samples) { string filename; if (names.ContainsKey(s.SampleArgs)) { filename = names[s.SampleArgs]; } else { // Validate the sample because we expect only valid samples to be present in the sample schema if (SampleImporter.ValidateSampleArgs(s.SampleArgs, loadedSamples, validateSampleFile)) { if (allowNamingGrowth) { HitsoundExporter.AddNewSampleName(names, s.SampleArgs, loadedSamples); filename = names[s.SampleArgs]; } else { throw new Exception($"Given sample schema doesn't support sample ({s.SampleArgs}) and growth is disabled."); } } else { filename = string.Empty; } } if (includeRegularHitsounds) { hitsounds.Add(new HitsoundEvent(p.Time, positions[s.SampleArgs], s.OutsideVolume, filename, s.SampleSet, s.SampleSet, 0, s.Whistle, s.Finish, s.Clap)); } else { hitsounds.Add(new HitsoundEvent(p.Time, positions[s.SampleArgs], s.OutsideVolume, filename, SampleSet.Auto, SampleSet.Auto, 0, false, false, false)); } } } return(hitsounds); }
/// <summary> /// Analyses all sound samples in a folder and generates a mapping from a full path without extension to the full path of the first sample which makes the same sound. /// Use this to detect duplicate samples. /// </summary> /// <param name="dir"></param> /// <param name="extended"></param> /// <param name="detectDuplicateSamples"></param> /// <returns></returns> public static Dictionary <string, string> AnalyzeSamples(string dir, bool extended = false, bool detectDuplicateSamples = true) { var extList = new[] { ".wav", ".ogg", ".mp3" }; List <string> samplePaths = Directory.GetFiles(dir, "*.*", extended ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly) .Where(n => extList.Contains(Path.GetExtension(n), StringComparer.OrdinalIgnoreCase)).ToList(); Dictionary <string, string> dict = new Dictionary <string, string>(); bool error = false; // Compare all samples to find ones with the same data if (detectDuplicateSamples) { for (int i = 0; i < samplePaths.Count; i++) { long thisLength = new FileInfo(samplePaths[i]).Length; for (int k = 0; k <= i; k++) { if (samplePaths[i] != samplePaths[k]) { long otherLength = new FileInfo(samplePaths[k]).Length; if (thisLength != otherLength) { continue; } try { using (var thisWave = SampleImporter.OpenSample(samplePaths[i])) { using (var otherWave = SampleImporter.OpenSample(samplePaths[k])) { if (thisWave.Length != otherWave.Length) { continue; } byte[] thisBuffer = new byte[thisWave.Length]; thisWave.Read(thisBuffer, 0, (int)thisWave.Length); byte[] otherBuffer = new byte[otherWave.Length]; otherWave.Read(otherBuffer, 0, (int)otherWave.Length); if (!thisBuffer.SequenceEqual(otherBuffer)) { continue; } } } } catch (Exception ex) { // Something went wrong reading the samples. I'll just assume they weren't the same if (!error) { MessageBox.Show($"Exception '{ex.Message}' while trying to analyze samples.", "Warning"); ex.Show(); error = true; } continue; } } string samplePath = samplePaths[i]; string fullPathExtLess = Path.Combine(Path.GetDirectoryName(samplePath) ?? throw new InvalidOperationException(), Path.GetFileNameWithoutExtension(samplePath)); dict[fullPathExtLess] = samplePaths[k]; break; } } } else { foreach (var samplePath in samplePaths) { string fullPathExtLess = Path.Combine(Path.GetDirectoryName(samplePath) ?? throw new InvalidOperationException(), Path.GetFileNameWithoutExtension(samplePath)); dict[fullPathExtLess] = samplePath; } } return(dict); }
public static void ExportMixedSample(IEnumerable <SampleGeneratingArgs> sampleGeneratingArgses, string name, string exportFolder, Dictionary <SampleGeneratingArgs, SampleSoundGenerator> loadedSamples = null) { var samples = new List <ISampleProvider>(); var volumes = new List <double>(); int soundsAdded = 0; if (loadedSamples != null) { foreach (var sample in from args in sampleGeneratingArgses where SampleImporter.ValidateSampleArgs(args, loadedSamples) select loadedSamples[args]) { samples.Add(sample.GetSampleProvider()); volumes.Add(Math.Abs(sample.VolumeCorrection - -1) > Precision.DOUBLE_EPSILON ? sample.VolumeCorrection : 1f); soundsAdded++; } } else { foreach (SampleGeneratingArgs args in sampleGeneratingArgses) { try { var sample = SampleImporter.ImportSample(args); samples.Add(sample.GetSampleProvider()); volumes.Add(Math.Abs(sample.VolumeCorrection - -1) > Precision.DOUBLE_EPSILON ? sample.VolumeCorrection : 1f); soundsAdded++; } catch (Exception ex) { Console.WriteLine($@"{ex.Message} while importing sample {args}."); } } } if (soundsAdded == 0) { return; } int maxSampleRate = samples.Max(o => o.WaveFormat.SampleRate); int maxChannels = samples.Max(o => o.WaveFormat.Channels); IEnumerable <ISampleProvider> sameFormatSamples = samples.Select(o => (ISampleProvider) new WdlResamplingSampleProvider(SampleImporter.SetChannels(o, maxChannels), maxSampleRate)); ISampleProvider result = new MixingSampleProvider(sameFormatSamples); if (soundsAdded > 1) { result = new VolumeSampleProvider(result) { Volume = (float)(1 / Math.Sqrt(soundsAdded * volumes.Average())) }; result = new SimpleCompressorEffect(result) { Threshold = 16, Ratio = 6, Attack = 0.1, Release = 0.1, Enabled = true, MakeUpGain = 15 * Math.Log10(Math.Sqrt(soundsAdded * volumes.Average())) }; } // TODO: Allow mp3, ogg and aif export. string filename = name + ".wav"; CreateWaveFile(Path.Combine(exportFolder, filename), result.ToWaveProvider16()); }
public static bool ExportSample(SampleGeneratingArgs sampleGeneratingArgs, string name, string exportFolder, Dictionary <SampleGeneratingArgs, SampleSoundGenerator> loadedSamples = null, SampleExportFormat format = SampleExportFormat.Default) { // Export as midi file with single note if (format == SampleExportFormat.MidiChords) { MidiExporter.SaveToFile(Path.Combine(exportFolder, name + ".mid"), new[] { sampleGeneratingArgs }); return(true); } if (sampleGeneratingArgs.CanCopyPaste && format == SampleExportFormat.Default) { var dest = Path.Combine(exportFolder, name + sampleGeneratingArgs.GetExtension()); return(CopySample(sampleGeneratingArgs.Path, dest)); } SampleSoundGenerator sampleSoundGenerator; if (loadedSamples != null) { if (SampleImporter.ValidateSampleArgs(sampleGeneratingArgs, loadedSamples)) { sampleSoundGenerator = loadedSamples[sampleGeneratingArgs]; } else { return(false); } } else { try { sampleSoundGenerator = SampleImporter.ImportSample(sampleGeneratingArgs); } catch (Exception ex) { Console.WriteLine($@"{ex.Message} while importing sample {sampleGeneratingArgs}."); return(false); } } var sourceWaveEncoding = sampleSoundGenerator.Wave.WaveFormat.Encoding; // Either if it is the blank sample or the source file is literally what the user wants to be exported if (sampleSoundGenerator.BlankSample && sampleGeneratingArgs.GetExtension().ToLower() == ".wav" || sampleGeneratingArgs.CanCopyPaste && IsCopyCompatible(sampleGeneratingArgs, sourceWaveEncoding, format)) { var dest = Path.Combine(exportFolder, name + sampleGeneratingArgs.GetExtension()); return(CopySample(sampleGeneratingArgs.Path, dest)); } var sampleProvider = sampleSoundGenerator.GetSampleProvider(); if ((format == SampleExportFormat.WavePcm || format == SampleExportFormat.OggVorbis) && sourceWaveEncoding == WaveFormatEncoding.IeeeFloat) { // When the source is IEEE float and the export format is PCM or Vorbis, then clipping is possible, so we add a limiter sampleProvider = new SoftLimiter(sampleProvider); } switch (format) { case SampleExportFormat.WaveIeeeFloat: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider()); break; case SampleExportFormat.WavePcm: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider16()); break; case SampleExportFormat.OggVorbis: var resampled = new WdlResamplingSampleProvider(sampleProvider, VorbisFileWriter.GetSupportedSampleRate(sampleProvider.WaveFormat.SampleRate)); VorbisFileWriter.CreateVorbisFile(Path.Combine(exportFolder, name + ".ogg"), resampled.ToWaveProvider()); break; default: switch (sourceWaveEncoding) { case WaveFormatEncoding.IeeeFloat: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider()); break; case WaveFormatEncoding.Pcm: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider16()); break; default: CreateWaveFile(Path.Combine(exportFolder, name + ".wav"), sampleProvider.ToWaveProvider()); break; } break; } return(true); }