// This method makes an approximation, namely that the pitch mark interval is roughly constant. // Usually, this will give a duration accurate to a few per cent (sufficient!) relative to the desired duration. public WAVSound ChangeDuration(WAVSound sound, List <double> pitchMarkTimeList, double relativeDuration) { /* List<double> pitchPeriodList = new List<double>(); * for (int ii = 1; ii < pitchMarkTimeList.Count; ii++) * { * double pitchPeriod = pitchMarkTimeList[ii] - pitchMarkTimeList[ii - 1]; * pitchPeriodList.Add(pitchPeriod); * } * double averagePitchPeriod = pitchPeriodList.Average(); */ List <short> newSamples = new List <short>(); int firstPitchMarkIndex = sound.GetSampleIndexAtTime(pitchMarkTimeList[0]); for (int ii = 0; ii < firstPitchMarkIndex; ii++) { newSamples.Add(sound.Samples[0][ii]); } if (relativeDuration <= 1) { int removalStepInterval = (int)Math.Round(1 / (1 - relativeDuration)); int pitchIndex = 1; while (pitchIndex < pitchMarkTimeList.Count) { if ((pitchIndex % removalStepInterval) == 0) { // Nothing to do here: Simply avoid adding these samples } else { int previousPitchMarkIndex = sound.GetSampleIndexAtTime(pitchMarkTimeList[pitchIndex - 1]); int currentPitchMarkIndex = sound.GetSampleIndexAtTime(pitchMarkTimeList[pitchIndex]); for (int ii = previousPitchMarkIndex; ii < currentPitchMarkIndex; ii++) { newSamples.Add(sound.Samples[0][ii]); } } pitchIndex++; } } else if (relativeDuration > 1) { int additionStepInterval = (int)Math.Round(1 / (relativeDuration - 1)); int pitchIndex = 1; while (pitchIndex < pitchMarkTimeList.Count) { if ((pitchIndex % additionStepInterval) == 0) { // Insert the samples for this pitch period twice: int previousPitchMarkIndex = sound.GetSampleIndexAtTime(pitchMarkTimeList[pitchIndex - 1]); int currentPitchMarkIndex = sound.GetSampleIndexAtTime(pitchMarkTimeList[pitchIndex]); for (int ii = previousPitchMarkIndex; ii < currentPitchMarkIndex; ii++) { newSamples.Add(sound.Samples[0][ii]); } for (int ii = previousPitchMarkIndex; ii < currentPitchMarkIndex; ii++) { newSamples.Add(sound.Samples[0][ii]); } } else { int previousPitchMarkIndex = sound.GetSampleIndexAtTime(pitchMarkTimeList[pitchIndex - 1]); int currentPitchMarkIndex = sound.GetSampleIndexAtTime(pitchMarkTimeList[pitchIndex]); for (int ii = previousPitchMarkIndex; ii < currentPitchMarkIndex; ii++) { newSamples.Add(sound.Samples[0][ii]); } } pitchIndex++; } } // Finally, build the sound from the new samples: WAVSound newSound = new WAVSound(sound.Name, sound.SampleRate, sound.NumberOfChannels, sound.BitsPerSample); newSound.GenerateFromSamples(new List <List <short> >() { newSamples, newSamples }); return(newSound); }
// Note: it is assumed that both channels (left and right) are equal. public WAVSound ChangePitch(WAVSound sound, List <double> pitchMarkTimeList, double relativeStartPitch, double relativeEndPitch) { // First find the pitch mark indices in the original sound: List <int> originalPitchMarkIndexList = new List <int>(); foreach (double pitchMarkTime in pitchMarkTimeList) { int originalPitchMarkIndex = sound.GetSampleIndexAtTime(pitchMarkTime); originalPitchMarkIndexList.Add(originalPitchMarkIndex); } // Next, compute the index spacings of the pitch marks in the modified sound: double originalSoundDuration = sound.GetDuration(); List <int> modifiedPitchMarkIndexSpacingList = new List <int>(); modifiedPitchMarkTimeList = new List <double>(); double firstModifiedPitchMarkTime = pitchMarkTimeList[0]; // First pitch mark unchanged modifiedPitchMarkTimeList.Add(firstModifiedPitchMarkTime); for (int ii = 1; ii < originalPitchMarkIndexList.Count; ii++) { int originalPitchMarkSpacing = originalPitchMarkIndexList[ii] - originalPitchMarkIndexList[ii - 1]; double relativePitch = relativeStartPitch + (pitchMarkTimeList[ii] / originalSoundDuration) * (relativeEndPitch - relativeStartPitch); int modifiedPitchMarkIndexSpacing = (int)Math.Round(originalPitchMarkSpacing / relativePitch); modifiedPitchMarkIndexSpacingList.Add(modifiedPitchMarkIndexSpacing); double modifiedPitchMarkTime = modifiedPitchMarkTimeList.Last() + (double)modifiedPitchMarkIndexSpacing / (double)sound.SampleRate; modifiedPitchMarkTimeList.Add(modifiedPitchMarkTime); } // Now build the sound, keeping the original sound data over a fraction (topFraction) of the pitch periods // and interpolating between pitch periods: List <short> newSamples = new List <short>(); // Special treatment of the first pitch period: int firstPitchMarkIndex = originalPitchMarkIndexList[0]; // Position of the first pitch mark in the original sound int firstModifiedPitchMarkIndexSpacing = modifiedPitchMarkIndexSpacingList[0]; // Spacing between the first and second pitch mark in the modified sound int firstTopEndIndex = firstPitchMarkIndex + (int)Math.Round(topFraction * firstModifiedPitchMarkIndexSpacing); for (int ii = 0; ii < firstTopEndIndex; ii++) { newSamples.Add(sound.Samples[0][ii]); } for (int iPitchMark = 1; iPitchMark < originalPitchMarkIndexList.Count; iPitchMark++) { // First add samples for the transition from the previous pitch period to the current one: int modifiedPitchMarkIndexSpacing = modifiedPitchMarkIndexSpacingList[iPitchMark - 1]; // -1 since there are n-1 spacings for n pitch marks int transitionIndexDuration = (int)Math.Round((1 - 2 * topFraction) * modifiedPitchMarkIndexSpacing); int previousPitchMarkIndex = originalPitchMarkIndexList[iPitchMark - 1]; int previousTopEndIndex = previousPitchMarkIndex + (int)Math.Round(topFraction * modifiedPitchMarkIndexSpacing); int startIndexPreviousPitchPeriod = previousTopEndIndex; int currentPitchMarkIndex = originalPitchMarkIndexList[iPitchMark]; int currentTopStartIndex = currentPitchMarkIndex - (int)Math.Round(topFraction * modifiedPitchMarkIndexSpacing); for (int ii = 0; ii < transitionIndexDuration; ii++) { double alpha = (double)ii / (double)(transitionIndexDuration - 1); int previousPitchPeriodSampleIndex = previousTopEndIndex + ii; int currentPitchPeriodSampleIndex = currentTopStartIndex - transitionIndexDuration + ii; short newSample = (short)Math.Round(((1 - alpha) * sound.Samples[0][previousPitchPeriodSampleIndex] + alpha * sound.Samples[0][currentPitchPeriodSampleIndex])); newSamples.Add(newSample); } // Next, add samples around the top of the current pitch period: if (iPitchMark < (originalPitchMarkIndexList.Count - 1)) { int nextModifiedPitchMarkIndexSpacing = modifiedPitchMarkIndexSpacingList[iPitchMark]; int currentTopEndIndex = currentPitchMarkIndex + (int)Math.Round(topFraction * nextModifiedPitchMarkIndexSpacing); for (int ii = currentTopStartIndex; ii < currentTopEndIndex; ii++) { newSamples.Add(sound.Samples[0][ii]); } } else // Special treatment of the final pitch period: { int endIndex = sound.Samples[0].Count; for (int ii = currentTopStartIndex; ii < endIndex; ii++) { newSamples.Add(sound.Samples[0][ii]); } } } // Finally, build the sound from the new samples: WAVSound newSound = new WAVSound(sound.Name, sound.SampleRate, sound.NumberOfChannels, sound.BitsPerSample); newSound.GenerateFromSamples(new List <List <short> >() { newSamples, newSamples }); return(newSound); }
public WAVSound GenerateSound(FormantSpecification formantSpecification) { double duration = formantSpecification.GetDuration(); int samplingFrequency = formantSpecification.SamplingFrequency; double sampleTime = 1.0 / (double)samplingFrequency; double nominalPulsePeriod = 1 / (double)formantSpecification.FundamentalFrequency; int numberOfSamples = (int)Math.Round(duration / sampleTime); // Generate voiced pulse train: This requires running through the sequence // of formantSettings in the formantSpecification, in order to determine // the pitch (and its inverse, the pulse period) at each time: List <int> voicedPulseSpacingList = formantSpecification.GeneratePulseSpacingList(); List <double> voicedPulseTrain = new List <double>(); for (int ii = 0; ii < numberOfSamples; ii++) { voicedPulseTrain.Add(0); } // To be adjusted below. int sampleIndex = 0; while (sampleIndex < voicedPulseTrain.Count) { voicedPulseTrain[sampleIndex] = 1.0; sampleIndex += voicedPulseSpacingList[sampleIndex]; } // Generate unvoiced pulse train: if (randomNumberGenerator == null) { randomNumberGenerator = new Random(); } // if (gaussian == null) { gaussian = new GaussianDistribution(0, 0.05, -1); } List <double> unvoicedPulseTrain = new List <double>(); for (int ii = 0; ii < numberOfSamples; ii++) { unvoicedPulseTrain.Add(0); if (randomNumberGenerator.NextDouble() < 0.5) { unvoicedPulseTrain[ii] = -WHITE_NOISE_LEVEL + 2 * WHITE_NOISE_LEVEL * randomNumberGenerator.NextDouble(); // gaussian.GetSample(); } } // Set up sinusoids: int numberOfSinusoids = formantSpecification.GetNumberOfSinusoids(); List <DampedSinusoid> sinusoidList = new List <DampedSinusoid>(); for (int iSinusoid = 0; iSinusoid < numberOfSinusoids; iSinusoid++) { DampedSinusoid sinusoid = new DampedSinusoid(samplingFrequency); sinusoidList.Add(sinusoid); } // Prepare for storing pitch: if (storePitch) { // pitchList = new List<double>(); timePitchPeriodList = new List <List <double> >(); } // Generate the relative amplitude list: Must be done separately, to handle // transitions: List <double> relativeAmplitudeList = formantSpecification.GenerateRelativeAmplitudeList(); // Generate the unscaled samples: List <double> unscaledSampleList = new List <double>(); double time = 0; sampleIndex = 0; while (sampleIndex < numberOfSamples) { time = sampleIndex * sampleTime; FormantSettings formantSettings = formantSpecification.GetInterpolatedSettings(sampleIndex); for (int iSinusoid = 0; iSinusoid < sinusoidList.Count; iSinusoid++) { sinusoidList[iSinusoid].SetParameters(formantSettings.AmplitudeList[iSinusoid], formantSettings.FrequencyList[iSinusoid], formantSettings.BandwidthList[iSinusoid]); } double x = formantSettings.VoicedFraction * voicedPulseTrain[sampleIndex] + (1 - formantSettings.VoicedFraction) * unvoicedPulseTrain[sampleIndex]; // 20170407 if (storePitch) { if (formantSettings.VoicedFraction > minimumVoicedFractionForPitch) { if (voicedPulseTrain[sampleIndex] != 0) // Define the pitch only at pulse spikes { double pitch = samplingFrequency / voicedPulseSpacingList[sampleIndex]; // pitchList.Add(pitch); double pitchPeriod = voicedPulseSpacingList[sampleIndex] * sampleTime; timePitchPeriodList.Add(new List <double>() { time, pitchPeriod }); } /* else { pitchList.Add(-1); } * } * else * { * pitchList.Add(-1); // < 0 => pitch not defined */ } } double deltaTime = formantSpecification.DeltaTimeList[sampleIndex]; double relativeAmplitude = relativeAmplitudeList[sampleIndex]; // formantSettings.GetRelativeAmplitude(deltaTime); double sample = 0; for (int iSinusoid = 0; iSinusoid < sinusoidList.Count; iSinusoid++) { sample += sinusoidList[iSinusoid].Next(x); } sample *= relativeAmplitude * volume; unscaledSampleList.Add(sample); sampleIndex++; } // Next generate the scaled samples List <Int16> sampleList = new List <Int16>(); for (int ii = 0; ii < numberOfSamples; ii++) { if (unscaledSampleList[ii] > 1) { unscaledSampleList[ii] = 1; } else if (unscaledSampleList[ii] < -1) { unscaledSampleList[ii] = -1; } Int16 sample = (Int16)Math.Round(32767 * unscaledSampleList[ii]); // Some ugly hard-coding here... sampleList.Add(sample); } List <List <Int16> > twoChannelSampleList = new List <List <Int16> >(); twoChannelSampleList.Add(sampleList); WAVSound wavSound = new WAVSound("Test", samplingFrequency, 1, 16); // Some ugly hard-coding here... wavSound.GenerateFromSamples(twoChannelSampleList); return(wavSound); }