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 Sample(HitsoundLayer hl) { _sampleArgs = hl.SampleArgs.Copy(); _priority = hl.Priority; _sampleSet = hl.SampleSet; _hitsound = hl.Hitsound; }
public Sample(SampleSet sampleSet, Hitsound hitsound, SampleGeneratingArgs sampleArgs, int priority) { _sampleArgs = sampleArgs; _priority = priority; _sampleSet = sampleSet; _hitsound = hitsound; }
public Sample() { _sampleArgs = new SampleGeneratingArgs(); _priority = 0; _sampleSet = SampleSet.Normal; _hitsound = Hitsound.Normal; }
public static bool ValidateSampleArgs(SampleGeneratingArgs args, Dictionary <SampleGeneratingArgs, SampleSoundGenerator> loadedSamples) { if (loadedSamples == null) { return(ValidateSampleArgs(args)); } return(loadedSamples.ContainsKey(args) && loadedSamples[args] != null); }
public static SampleSoundGenerator ImportFromVorbis(SampleGeneratingArgs args) { var generator = new SampleSoundGenerator(new VorbisWaveReader(args.Path)) { VolumeCorrection = (float)args.Volume }; return(generator); }
private static bool IsCopyCompatible(SampleGeneratingArgs sampleGeneratingArgs, WaveFormatEncoding waveEncoding, SampleExportFormat exportFormat) { switch (exportFormat) { case SampleExportFormat.WaveIeeeFloat: return(waveEncoding == WaveFormatEncoding.IeeeFloat && sampleGeneratingArgs.GetExtension() == ".wav"); case SampleExportFormat.WavePcm: return(waveEncoding == WaveFormatEncoding.Pcm && sampleGeneratingArgs.GetExtension() == ".wav"); case SampleExportFormat.OggVorbis: return(sampleGeneratingArgs.GetExtension() == ".ogg"); default: return(true); } }
private static Zone ImportInstrument(Instrument i, SampleGeneratingArgs args) { Zone closest = null; int bdist = int.MaxValue; // an Instrument contains a set of zones that contain sample headers. foreach (var instrumentZone in i.Zones) { var sh = instrumentZone.SampleHeader(); if (sh == null) { continue; } // Requested key/velocity must also fit in the key/velocity range of the sample ushort keyRange = instrumentZone.KeyRange(); byte keyLow = (byte)keyRange; byte keyHigh = (byte)(keyRange >> 8); if (!(args.Key >= keyLow && args.Key <= keyHigh) && args.Key != -1 && keyRange != 0) { continue; } ushort velRange = instrumentZone.VelocityRange(); byte velLow = (byte)keyRange; byte velHigh = (byte)(keyRange >> 8); if (!(args.Velocity >= velLow && args.Velocity <= velHigh) && args.Velocity != -1 && velRange != 0) { continue; } // Get the closest key possible int dist = Math.Abs(args.Key - instrumentZone.Key()); if (dist < bdist || args.Key == -1) { closest = instrumentZone; bdist = dist; } } return(closest); }
public static SampleSoundGenerator ImportSample(SampleGeneratingArgs args) { string path = args.Path; if (Path.GetExtension(path) == ".sf2") { SoundFont sf2 = new SoundFont(path); SampleSoundGenerator wave = ImportFromSoundFont(args, sf2); GC.Collect(); return(wave); } else if (Path.GetExtension(path) == ".ogg") { return(ImportFromVorbis(args)); } else { return(ImportFromAudio(args)); } }
public static SampleSoundGenerator ImportSample(SampleGeneratingArgs args) { string path = args.Path; switch (Path.GetExtension(path)) { case ".sf2": { SoundFont sf2 = new SoundFont(path); SampleSoundGenerator wave = ImportFromSoundFont(args, sf2); GC.Collect(); return(wave); } case ".ogg": return(ImportFromVorbis(args)); default: return(ImportFromAudio(args)); } }
// TODO: format soundfont import to detect file versions and types of files. public static SampleSoundGenerator ImportFromSoundFont(SampleGeneratingArgs args, SoundFont sf2) { SampleSoundGenerator wave = null; foreach (var preset in sf2.Presets) { if (preset.PatchNumber != args.Patch && args.Patch != -1) { continue; } if (preset.Bank != args.Bank && args.Bank != -1) { continue; } wave = ImportPreset(sf2, preset, args); if (wave != null) { break; } } return(wave ?? ImportInstruments(sf2, args)); }
private static SampleSoundGenerator GetSampleRemainder(SampleHeader sh, Zone izone, byte[] sample, SampleGeneratingArgs args) { // Indices in sf2 are numbers of samples, not byte length. So double them int start = (int)sh.Start + izone.FullStartAddressOffset(); int end = (int)sh.End + izone.FullEndAddressOffset(); int startLoop = (int)sh.StartLoop + izone.FullStartLoopAddressOffset(); int endLoop = (int)sh.EndLoop + izone.FullEndLoopAddressOffset(); int length = end - start; int loopLength = endLoop - startLoop; int loopLengthBytes = loopLength * 2; int lengthFirstHalf = startLoop - start; int lengthFirstHalfBytes = lengthFirstHalf * 2; int lengthSecondHalf = end - endLoop; int lengthSecondHalfBytes = lengthSecondHalf * 2; double lengthInSeconds = args.Length != -1 ? (args.Length / 1000) : length / (double)sh.SampleRate; // Sample rate key correction int keyCorrection = args.Key != -1 ? args.Key - izone.Key() : 0; double factor = Math.Pow(2, keyCorrection / 12d); lengthInSeconds *= factor; int numberOfSamples = (int)Math.Ceiling(lengthInSeconds * sh.SampleRate); numberOfSamples += lengthSecondHalf; int numberOfLoopSamples = numberOfSamples - lengthFirstHalf - lengthSecondHalf; if (numberOfLoopSamples < loopLength) { return(GetSampleWithoutLoop(sh, izone, sample, args)); } int numberOfBytes = numberOfSamples * 2; int numberOfLoopBytes = numberOfLoopSamples * 2; byte[] buffer = new byte[numberOfBytes]; byte[] bufferLoop = new byte[numberOfLoopBytes]; Array.Copy(sample, start * 2, buffer, 0, lengthFirstHalfBytes); for (int i = 0; i < (numberOfLoopSamples + loopLength - 1) / loopLength; i++) { Array.Copy(sample, startLoop * 2, bufferLoop, i * loopLengthBytes, Math.Min(loopLengthBytes, numberOfLoopBytes - i * loopLengthBytes)); } bufferLoop.CopyTo(buffer, lengthFirstHalfBytes); Array.Copy(sample, start * 2, buffer, lengthFirstHalfBytes + numberOfLoopBytes, lengthSecondHalfBytes); return(new SampleSoundGenerator(BufferToWaveStream(buffer, (uint)(sh.SampleRate * factor)))); }
private static SampleSoundGenerator GetSampleWithoutLoop(SampleHeader sh, Zone izone, byte[] sample, SampleGeneratingArgs args) { // Indices in sf2 are numbers of samples, not byte length. So double them int start = (int)sh.Start + izone.FullStartAddressOffset(); int end = (int)sh.End + izone.FullEndAddressOffset(); int length = end - start; double lengthInSeconds = args.Length != -1 ? (args.Length / 1000) + 0.4 : length / (double)sh.SampleRate; // Sample rate key correction int keyCorrection = args.Key != -1 ? args.Key - izone.Key() : 0; double factor = Math.Pow(2, keyCorrection / 12d); lengthInSeconds *= factor; lengthInSeconds = Math.Min(lengthInSeconds, length / (double)sh.SampleRate); int numberOfSamples = (int)Math.Ceiling(lengthInSeconds * sh.SampleRate); int numberOfBytes = numberOfSamples * 2; byte[] buffer = new byte[numberOfBytes]; Array.Copy(sample, start * 2, buffer, 0, numberOfBytes); var output = new SampleSoundGenerator(BufferToWaveStream(buffer, (uint)(sh.SampleRate * factor))); if (lengthInSeconds <= 0.4) { output.FadeStart = lengthInSeconds * 0.7; output.FadeLength = lengthInSeconds * 0.2; } else { output.FadeStart = lengthInSeconds - 0.4; output.FadeLength = 0.3; } return(output); }
private static SampleSoundGenerator GetSampleWithLength(SampleHeader sh, Zone izone, int sampleMode, byte[] sample, SampleGeneratingArgs args) { if (sampleMode == 0 || sampleMode == 2) { // Don't loop return(GetSampleWithoutLoop(sh, izone, sample, args)); } else if (sampleMode == 1) { // Loop continuously return(GetSampleContinuous(sh, izone, sample, args)); } else { // Loops for the duration of key depression then proceed to play the remainder of the sample return(GetSampleRemainder(sh, izone, sample, args)); } }
private static SampleSoundGenerator GenerateSample(Zone izone, byte[] sample, SampleGeneratingArgs args) { // Read the sample mode to apply the correct lengthening algorithm // Add volume sample provider for the velocity argument var sh = izone.SampleHeader(); int sampleMode = izone.SampleModes(); byte key = izone.Key(); byte velocity = izone.Velocity(); double volumeCorrection = args.Velocity != -1 ? (double)args.Velocity / velocity : 1d; var output = GetSampleWithLength(sh, izone, sampleMode, sample, args); // Key correction is 0 to not use the shitty pitch shifter output.KeyCorrection = 0; output.VolumeCorrection = volumeCorrection; return(output); }
public static bool ValidateSampleArgs(SampleGeneratingArgs args, bool validateSampleFile = true) { return(!validateSampleFile || ValidateSamplePath(args.Path)); }
public static List <HitsoundLayer> ImportMidi(string path, double offset = 0, bool instruments = true, bool keysounds = true, bool lengths = true, double lengthRoughness = 1, bool velocities = true, double velocityRoughness = 1) { List <HitsoundLayer> hitsoundLayers = new List <HitsoundLayer>(); var strictMode = false; var mf = new MidiFile(path, strictMode); Console.WriteLine( $@"Format {mf.FileFormat}, " + $@"Tracks {mf.Tracks}, " + $@"Delta Ticks Per Quarter Note {mf.DeltaTicksPerQuarterNote}"); List <TempoEvent> tempos = new List <TempoEvent>(); foreach (var track in mf.Events) { tempos.AddRange(track.OfType <TempoEvent>()); } tempos = tempos.OrderBy(o => o.AbsoluteTime).ToList(); List <double> cumulativeTime = CalculateCumulativeTime(tempos, mf.DeltaTicksPerQuarterNote); Dictionary <int, int> channelBanks = new Dictionary <int, int>(); Dictionary <int, int> channelPatches = new Dictionary <int, int>(); // Loop through every event of every track for (int track = 0; track < mf.Tracks; track++) { foreach (var midiEvent in mf.Events[track]) { if (midiEvent is PatchChangeEvent pc) { channelPatches[pc.Channel] = pc.Patch; } else if (midiEvent is ControlChangeEvent co) { if (co.Controller == MidiController.BankSelect) { channelBanks[co.Channel] = (co.ControllerValue * 128) + (channelBanks.ContainsKey(co.Channel) ? (byte)channelBanks[co.Channel] : 0); } else if (co.Controller == MidiController.BankSelectLsb) { channelBanks[co.Channel] = co.ControllerValue + (channelBanks.ContainsKey(co.Channel) ? channelBanks[co.Channel] >> 8 * 128 : 0); } } else if (MidiEvent.IsNoteOn(midiEvent)) { var on = midiEvent as NoteOnEvent; double time = CalculateTime(on.AbsoluteTime, tempos, cumulativeTime, mf.DeltaTicksPerQuarterNote); double length = on.OffEvent != null ? CalculateTime(on.OffEvent.AbsoluteTime, tempos, cumulativeTime, mf.DeltaTicksPerQuarterNote) - time : -1; length = RoundLength(length, lengthRoughness); bool keys = keysounds || on.Channel == 10; int bank = instruments ? on.Channel == 10 ? 128 : channelBanks.ContainsKey(on.Channel) ? channelBanks[on.Channel] : 0 : -1; int patch = instruments && channelPatches.ContainsKey(on.Channel) ? channelPatches[on.Channel] : -1; int instrument = -1; int key = keys ? on.NoteNumber : -1; length = lengths ? length : -1; int velocity = velocities ? on.Velocity : -1; velocity = (int)RoundVelocity(velocity, velocityRoughness); string lengthString = Math.Round(length).ToString(CultureInfo.InvariantCulture); string filename = $"{bank}\\{patch}\\{instrument}\\{key}\\{lengthString}\\{velocity}.wav"; string instrumentName = on.Channel == 10 ? "Percussion" : patch >= 0 && patch <= 127 ? PatchChangeEvent.GetPatchName(patch) : "Undefined"; string keyName = on.NoteName; string name = instrumentName; if (keysounds) { name += "," + keyName; } if (lengths) { name += "," + lengthString; } if (velocities) { name += "," + velocity; } var sampleArgs = new SampleGeneratingArgs(filename, bank, patch, instrument, key, length, velocity); var importArgs = new LayerImportArgs(ImportType.MIDI) { Path = path, Bank = bank, Patch = patch, Key = key, Length = length, LengthRoughness = lengthRoughness, Velocity = velocity, VelocityRoughness = velocityRoughness }; // Find the hitsoundlayer with this path HitsoundLayer layer = hitsoundLayers.Find(o => o.ImportArgs == importArgs); if (layer != null) { // Find hitsound layer with this path and add this time layer.Times.Add(time + offset); } else { // Add new hitsound layer with this path HitsoundLayer newLayer = new HitsoundLayer(name, SampleSet.Normal, Hitsound.Normal, sampleArgs, importArgs); newLayer.Times.Add(time + offset); hitsoundLayers.Add(newLayer); } } } } // Stretch the velocities to reach 127 int maxVelocity = hitsoundLayers.Max(o => o.SampleArgs.Velocity); foreach (var hsl in hitsoundLayers) { hsl.SampleArgs.Velocity = (int)Math.Round(hsl.SampleArgs.Velocity / (float)maxVelocity * 127); } // Sort the times hitsoundLayers.ForEach(o => o.Times = o.Times.OrderBy(t => t).ToList()); // Sort layers by name hitsoundLayers = hitsoundLayers.OrderBy(o => o.Name).ToList(); return(hitsoundLayers); }
public static bool ValidateSampleArgs(SampleGeneratingArgs args) { return(ValidateSampleArgs(args.Path)); }
private static SampleSoundGenerator ImportInstruments(SoundFont sf2, IEnumerable <Instrument> instruments, SampleGeneratingArgs args) { Zone closest = null; int i = 0; int bdist = int.MaxValue; foreach (var instrument in instruments) // perccusion bank likely has more than one instrument here. { if (instrument == null) { continue; } if (i++ != args.Instrument && args.Instrument != -1) { continue; } var iZone = ImportInstrument(instrument, args); if (iZone == null) { continue; } // Get closest instrument from the zones in the preset int dist = Math.Abs(args.Key - iZone.Key()); if (dist < bdist || args.Key == -1) { closest = iZone; bdist = dist; } } if (closest == null) { return(null); } //Console.WriteLine("closest: " + closest.Key()); var wave = GenerateSample(closest, sf2.SampleData, args); return(wave); }
private static SampleSoundGenerator ImportInstruments(SoundFont sf2, SampleGeneratingArgs args) { return(ImportInstruments(sf2, sf2.Instruments, args)); }
private static SampleSoundGenerator ImportPreset(SoundFont sf2, Preset preset, SampleGeneratingArgs args) { /* * == Aproximate Pesdo Code of importing a preset from sf2 == * - Get all preset zones from soundfont (sf2) * - get the instrument and double check if it's not null (if it is, "continue" the method) * - is the instrument grabbed the same as what args is asking for? (if not, "continue" method) * - get each zone within instrument and create wav file from information * - get Sample Header of instrument zone and double check if null (if so, "continue") * - get the Key range of spesified zone and create high and low keys 8 bit's in difference. * - is the key not the same as what the args asked for? (yes, then "continue") * - Get the velocity range of spesified zone and create high and low keys 8 bits' in difference. * - is the velocity not the same as what the args asked for? (yes, then "continue") * - Find the closest key from instrument zone to the key spesified from args. * - is the closest key lower than the maximum integer value or, is the key of args just not spesified? * - if so, set the closest zone (initial = null) to the current zone, * - if so, set the bdist (initial = maximum integer value) to the closest key. * - Is there a zone found from above? * - If so, create a wave from zone information using the SampleData. */ return(ImportInstruments(sf2, preset.Zones.Select(z => z.Instrument()), args)); }
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); }
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; }
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); }