/// <summary> /// Allows for quick initialization of an empty audio stream when creating and modifying audio from scratch. /// </summary> /// <param name="audioFormat">The format of the new audio stream to work with.</param> /// <returns>A "List" object of PCMSample objects containing only a single, empty sample.</returns> /// <see cref="PCMSample"/> public static List <PCMSample> InitializeStream(AudioFormat audioFormat) { return(new List <PCMSample>(new PCMSample[] { new PCMSample(audioFormat, 0) })); }
/// <summary> /// Mixes different audio streams, represented as lists of PCMSample objects and their corresponding amplitude weightings, into an overall stream of sound. /// The resulting stream duration will equal the longest stream in the "samples" list, so varying stream durations can be easily mixed together; simply note that /// the final stream starts all component streams at the "0" duration mark. /// </summary> /// <param name="audioFormat">The format used for aggregating all stream data. This MUST be the same format used across all streams -- any stream NOT matching the provided format will be DISCARDED from the final result.</param> /// <param name="peakOutputAmplitudePercentage">A scaling percentage for the final stream's peak output amplitude. Defaults to 100%, which represents no change in the mixed stream's volume.</param> /// <param name="componentStreamCollection">Any amount of tuples consisting of the amplitude (volume) weighting within all mixed samples, and the audio sample itself. NOTE: The weighting is NOT a percentage value!</param> /// <returns>An audio stream (stream of samples) equal to the combination of all provided component streams at their given amplitude weightings.</returns> /// <see cref="PCMSample"/> /// <seealso cref="InterlaceSamples(AudioFormat, ref List{PCMSample}, (double startTimeSeconds, double amplitudePercentage, List{PCMSample} audioStream)[])"/> public static List <PCMSample> MixSamples( AudioFormat audioFormat, double peakOutputAmplitudePercentage = 100.0, params (double, List <PCMSample>)[] componentStreamCollection
/// <summary> /// Generates a new wave-form as an audio stream (list of PCMSample objects). /// </summary> /// <param name="audioFormat">The format used to sample the waveform.</param> /// <param name="wavePattern">The type of wave to generate.</param> /// <param name="amplitudePerc">A percentage of the audio format's MaxAmplitude (0 to 100%).</param> /// <param name="durationSec">How long (in seconds) the sound will be generated for.</param> /// <param name="frequencyHz">The frequency at which the sound should be played.</param> /// <returns>A new list of PCMSample objects that represent the sound.</returns> /// <see cref="PCMSample"/> /// <see cref="AudioFormat"/> public static List <PCMSample> CreateNewWave(AudioFormat audioFormat, WaveType wavePattern, double amplitudePerc, double durationSec, double frequencyHz) { // Cap the amplitude at 100%. amplitudePerc = Math.Min(100.0, Math.Abs(amplitudePerc)); // Samples required to complete one wave period at the given frequency. double samplesPerWaveCycle = (double)(audioFormat.SampleRate / frequencyHz); // Total amount of samples for the wave duration in its entirety. double totalSamples = durationSec * (double)audioFormat.SampleRate; // Additional amount of samples to append onto the stream until the final wave/period completes. // This is aimed at keeping sudden adjacent tones from causing hard key clicks when transitioning. double extraSamples = samplesPerWaveCycle - (totalSamples % samplesPerWaveCycle); double waveAttenuationSamplesCount = (samplesPerWaveCycle * WaveGenerator.ATTENUATION_CYCLES); // Short-handing some variables from the audio format as needed... int sampleRate = audioFormat.SampleRate; // Get the peak amplitude permitted based on the amplitudePerc parameter. double peakAmplitude = audioFormat.GetPeakAmplitude(amplitudePerc); // Initialize the new stream object. var newWaveStream = new List <PCMSample>(); // Create the wave stream based on the requested wave type. var prng = new Random(); //PRNG as needed for noise and dither Func <int, double> sampleCalculation = wavePattern switch { // f(x) = amplitude * SIN(2pi * x * frequency / sampleRate) WaveType.SINE => (currSample => peakAmplitude * Math.Sin((2 * Math.PI * currSample * frequencyHz) / sampleRate) ), // f(x) = abs(amplitude), when 0 <= x < period/2 ;; f(x) = neg(amplitude), when period/2 <= x < period // My implementation factors amplitude out of: f(x) = (({2*[amp/samplesPerPeriod]*x + amp} % [amp*2]) - (amp) WaveType.SAWTOOTH => (currSample => ((peakAmplitude * ((2 * currSample / samplesPerWaveCycle) + 1)) % (peakAmplitude * 2)) - peakAmplitude ), // f(x) = x, when 0 <= x < pi ;; f(x) = x-2pi, when pi <= x =< 2pi WaveType.SQUARE => (currSample => (currSample % samplesPerWaveCycle) < (samplesPerWaveCycle / 2) ? peakAmplitude : 0 - peakAmplitude ), // f(x) = [2*amp / pi] * [ arcsin( sin( {2pi * freq}/sampleRate ) * x ) ] // Definitely had to look this one up... WaveType.TRIANGLE => (currSample => ((2 * peakAmplitude) / Math.PI) * Math.Asin(Math.Sin(((2 * Math.PI * frequencyHz) / sampleRate) * currSample)) ), // f(x) = random(x); f(x) constrained to [-MinPeak, MaxPeak] WaveType.WHITE_NOISE => (currSample => (prng.NextDouble() * (audioFormat.MaxSampleValue - audioFormat.MinSampleValue)) + audioFormat.MinSampleValue ), // If no other enum matched, throw exception _ => throw new Exception("CreateNewWave: Invalid wave pattern selection: " + wavePattern.ToString()) }; for (int currentSample = 0; currentSample < totalSamples + extraSamples; currentSample++) { double sampleValue = sampleCalculation(currentSample); if (currentSample < waveAttenuationSamplesCount) { // For the first few wave cycles, attenuate the max amplitude by a // ratio to gradually introduce the signal. // Again, this is aimed to help prevent clicking in the resulting sample sampleValue *= (double)((double)currentSample / waveAttenuationSamplesCount); } else if (currentSample > ((totalSamples + extraSamples) - waveAttenuationSamplesCount)) { // Likewise, attenuate the end of the signal for the sample. sampleValue *= (double)((double)((totalSamples + extraSamples) - currentSample) / waveAttenuationSamplesCount); } newWaveStream.Add(new PCMSample(audioFormat, (int)sampleValue)); } // Finally, return the new stream. return(newWaveStream); } }