├── Example.bmp ├── NAudio Pitchshifter Test.csproj.user ├── bin ├── Debug │ ├── NAudio.dll │ ├── NAudio Pitchshifter Test.exe │ ├── NAudio Pitchshifter Test.pdb │ └── NAudio Pitchshifter Test.vshost.exe └── Release │ ├── NAudio.dll │ ├── NAudio Pitchshifter Test.exe │ └── NAudio Pitchshifter Test.pdb ├── NAudio Pitchshifter Test.suo ├── Properties ├── Settings.settings ├── Settings.Designer.cs ├── AssemblyInfo.cs ├── Resources.Designer.cs └── Resources.resx ├── Program.cs ├── NAudio Pitchshifter Test.sln ├── README.md ├── FormMain.cs ├── NAudio Pitchshifter Test.csproj ├── FormMain.Designer.cs ├── SMBPitchShiftingSampleProvider.cs ├── FormMain.resx └── SMBPitchShifter.cs /Example.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freefall63/NAudio-Pitchshifter/HEAD/Example.bmp -------------------------------------------------------------------------------- /NAudio Pitchshifter Test.csproj.user: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/Debug/NAudio.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freefall63/NAudio-Pitchshifter/HEAD/bin/Debug/NAudio.dll -------------------------------------------------------------------------------- /bin/Release/NAudio.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freefall63/NAudio-Pitchshifter/HEAD/bin/Release/NAudio.dll -------------------------------------------------------------------------------- /NAudio Pitchshifter Test.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freefall63/NAudio-Pitchshifter/HEAD/NAudio Pitchshifter Test.suo -------------------------------------------------------------------------------- /bin/Debug/NAudio Pitchshifter Test.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freefall63/NAudio-Pitchshifter/HEAD/bin/Debug/NAudio Pitchshifter Test.exe -------------------------------------------------------------------------------- /bin/Debug/NAudio Pitchshifter Test.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freefall63/NAudio-Pitchshifter/HEAD/bin/Debug/NAudio Pitchshifter Test.pdb -------------------------------------------------------------------------------- /bin/Release/NAudio Pitchshifter Test.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freefall63/NAudio-Pitchshifter/HEAD/bin/Release/NAudio Pitchshifter Test.exe -------------------------------------------------------------------------------- /bin/Release/NAudio Pitchshifter Test.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freefall63/NAudio-Pitchshifter/HEAD/bin/Release/NAudio Pitchshifter Test.pdb -------------------------------------------------------------------------------- /bin/Debug/NAudio Pitchshifter Test.vshost.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Freefall63/NAudio-Pitchshifter/HEAD/bin/Debug/NAudio Pitchshifter Test.vshost.exe -------------------------------------------------------------------------------- /Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows.Forms; 5 | 6 | namespace NAudio_Pitchshifter_Test 7 | { 8 | static class Program 9 | { 10 | /// 11 | /// Der Haupteinstiegspunkt für die Anwendung. 12 | /// 13 | [STAThread] 14 | static void Main() 15 | { 16 | Application.EnableVisualStyles(); 17 | Application.SetCompatibleTextRenderingDefault(false); 18 | Application.Run(new FormMain()); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /NAudio Pitchshifter Test.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 10.00 3 | # Visual Studio 2008 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAudio Pitchshifter Test", "NAudio Pitchshifter Test.csproj", "{EF8F9430-A62D-4C3D-95FB-CF6FF4B32F2B}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {EF8F9430-A62D-4C3D-95FB-CF6FF4B32F2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {EF8F9430-A62D-4C3D-95FB-CF6FF4B32F2B}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {EF8F9430-A62D-4C3D-95FB-CF6FF4B32F2B}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {EF8F9430-A62D-4C3D-95FB-CF6FF4B32F2B}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:2.0.50727.5448 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace NAudio_Pitchshifter_Test.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NAudio-Pitchshifter 2 | 3 | A realtime C# audio pitch shifter based on S. M. Bernsees phase vocoder that implements an NAudio ISampleProvider. 4 | 5 | Or in simple words: this lets you change the pitch of audio (e.g. in realtime playback using the NAudio library). 6 | 7 | UPDATE: contributed the newest version to NAudio, where it is now included from V1.8 on: 8 | https://github.com/naudio/NAudio/blob/master/NAudio/Wave/SampleProviders/SMBPitchShiftingSampleProvider.cs 9 | 10 | 11 | Test application screenshot: 12 | 13 | 14 | ![Example Screenshot](Example.bmp) 15 | 16 | ________________________________________________________________________________________ 17 | 18 | 19 | Usage 20 | 21 | ________________________________________________________________________________________ 22 | 23 | 24 | Imports NAudio.Wave 25 | 26 | // Choose FFTSize and Osamp. (recommended are 4096 and 4) 27 | // Define Pitch shifting factor. (0.5f pitches one octave down, 2f would pitch one octave up) 28 | 29 | Sub Test() 30 | 31 | SMBPitchShiftingSampleProvider SMB = new SMBPitchShiftingSampleProvider(new AudioFileReader(@"C:\Test.wav"), 4096, 4L, 0.5f); 32 | 33 | WaveOutEvent wo = new WaveOutEvent 34 | { 35 | DesiredLatency = 150, 36 | NumberOfBuffers = 3 37 | }; 38 | 39 | wo.Init(new SampleToWaveProvider16(SMB)); 40 | 41 | wo.Play(); 42 | 43 | End Sub 44 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Allgemeine Informationen über eine Assembly werden über die folgenden 6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 7 | // die mit einer Assembly verknüpft sind. 8 | [assembly: AssemblyTitle("NAudio Pitchshifter Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("NAudio Pitchshifter Test")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 18 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 19 | // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. 20 | [assembly: ComVisible(false)] 21 | 22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 23 | [assembly: Guid("02b8e433-9003-4485-ad1c-f611ee5a017b")] 24 | 25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 26 | // 27 | // Hauptversion 28 | // Nebenversion 29 | // Buildnummer 30 | // Revision 31 | // 32 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 33 | // übernehmen, indem Sie "*" eingeben: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /FormMain.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Forms; 9 | using NAudio.Wave; 10 | using NAudio.Wave.SampleProviders; 11 | 12 | namespace NAudio_Pitchshifter_Test 13 | { 14 | public partial class FormMain : Form 15 | { 16 | public FormMain() 17 | { 18 | InitializeComponent(); 19 | } 20 | 21 | SMBPitchShiftingSampleProvider SMB; 22 | float Pitch = 1f; 23 | 24 | private void trackBar1_Scroll(object sender, EventArgs e) 25 | { 26 | if (trackBar1.Value > 11) 27 | { 28 | Pitch = (float)((trackBar1.Value - 1) / 10f); 29 | } 30 | else if (trackBar1.Value < 11) 31 | { 32 | Pitch = (float)(((trackBar1.Value - 1) / 10f * 0.5f) + 0.5f); 33 | } 34 | else 35 | { 36 | Pitch = 1f; 37 | } 38 | button1.Text = Pitch.ToString(); 39 | if (!Object.ReferenceEquals(null, SMB)) 40 | { 41 | SMB.PitchFactor = Pitch; 42 | } 43 | } 44 | 45 | private void button2_Click(object sender, EventArgs e) 46 | { 47 | OpenFileDialog OFD = new OpenFileDialog(); 48 | if (OFD.ShowDialog() == DialogResult.OK) 49 | { 50 | SMB = new SMBPitchShiftingSampleProvider(new AudioFileReader(OFD.FileName), 4096, 8L, Pitch); 51 | WaveOutEvent wo = new WaveOutEvent 52 | { 53 | DesiredLatency = 150, 54 | NumberOfBuffers = 3 55 | }; 56 | wo.Init(new SampleToWaveProvider16(SMB)); 57 | wo.Play(); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Dieser Code wurde von einem Tool generiert. 4 | // Laufzeitversion:2.0.50727.5448 5 | // 6 | // Änderungen an dieser Datei können fehlerhaftes Verhalten verursachen und gehen verloren, wenn 7 | // der Code neu generiert wird. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace NAudio_Pitchshifter_Test.Properties 12 | { 13 | 14 | 15 | /// 16 | /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. 17 | /// 18 | // Diese Klasse wurde von der StronglyTypedResourceBuilder-Klasse 19 | // über ein Tool wie ResGen oder Visual Studio automatisch generiert. 20 | // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen 21 | // mit der Option /str erneut aus, oder erstellen Sie Ihr VS-Projekt neu. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NAudio_Pitchshifter_Test.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle 56 | /// Ressourcenlookups, die diese stark typisierte Ressourcenklasse verwenden. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /NAudio Pitchshifter Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.21022 7 | 2.0 8 | {EF8F9430-A62D-4C3D-95FB-CF6FF4B32F2B} 9 | WinExe 10 | Properties 11 | NAudio_Pitchshifter_Test 12 | NAudio Pitchshifter Test 13 | v3.5 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 3.5 38 | 39 | 40 | 3.5 41 | 42 | 43 | 3.5 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Form 54 | 55 | 56 | FormMain.cs 57 | 58 | 59 | 60 | 61 | 62 | FormMain.cs 63 | Designer 64 | 65 | 66 | ResXFileCodeGenerator 67 | Resources.Designer.cs 68 | Designer 69 | 70 | 71 | True 72 | Resources.resx 73 | 74 | 75 | SettingsSingleFileGenerator 76 | Settings.Designer.cs 77 | 78 | 79 | True 80 | Settings.settings 81 | True 82 | 83 | 84 | 85 | 86 | 93 | -------------------------------------------------------------------------------- /FormMain.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace NAudio_Pitchshifter_Test 2 | { 3 | partial class FormMain 4 | { 5 | /// 6 | /// Erforderliche Designervariable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Verwendete Ressourcen bereinigen. 12 | /// 13 | /// True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Vom Windows Form-Designer generierter Code 24 | 25 | /// 26 | /// Erforderliche Methode für die Designerunterstützung. 27 | /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.trackBar1 = new System.Windows.Forms.TrackBar(); 32 | this.label1 = new System.Windows.Forms.Label(); 33 | this.button1 = new System.Windows.Forms.Button(); 34 | this.button2 = new System.Windows.Forms.Button(); 35 | ((System.ComponentModel.ISupportInitialize)(this.trackBar1)).BeginInit(); 36 | this.SuspendLayout(); 37 | // 38 | // trackBar1 39 | // 40 | this.trackBar1.Location = new System.Drawing.Point(17, 118); 41 | this.trackBar1.Maximum = 21; 42 | this.trackBar1.Minimum = 1; 43 | this.trackBar1.Name = "trackBar1"; 44 | this.trackBar1.Size = new System.Drawing.Size(259, 45); 45 | this.trackBar1.TabIndex = 0; 46 | this.trackBar1.Value = 11; 47 | this.trackBar1.Scroll += new System.EventHandler(this.trackBar1_Scroll); 48 | // 49 | // label1 50 | // 51 | this.label1.AutoSize = true; 52 | this.label1.Location = new System.Drawing.Point(114, 86); 53 | this.label1.Name = "label1"; 54 | this.label1.Size = new System.Drawing.Size(61, 13); 55 | this.label1.TabIndex = 1; 56 | this.label1.Text = "PitchFactor"; 57 | // 58 | // button1 59 | // 60 | this.button1.Enabled = false; 61 | this.button1.FlatStyle = System.Windows.Forms.FlatStyle.Popup; 62 | this.button1.Location = new System.Drawing.Point(108, 164); 63 | this.button1.Name = "button1"; 64 | this.button1.Size = new System.Drawing.Size(75, 23); 65 | this.button1.TabIndex = 2; 66 | this.button1.Text = "1"; 67 | this.button1.UseVisualStyleBackColor = true; 68 | // 69 | // button2 70 | // 71 | this.button2.Location = new System.Drawing.Point(89, 26); 72 | this.button2.Name = "button2"; 73 | this.button2.Size = new System.Drawing.Size(117, 23); 74 | this.button2.TabIndex = 3; 75 | this.button2.Text = "Open file"; 76 | this.button2.UseVisualStyleBackColor = true; 77 | this.button2.Click += new System.EventHandler(this.button2_Click); 78 | // 79 | // FormMain 80 | // 81 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 82 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 83 | this.ClientSize = new System.Drawing.Size(292, 209); 84 | this.Controls.Add(this.button2); 85 | this.Controls.Add(this.button1); 86 | this.Controls.Add(this.label1); 87 | this.Controls.Add(this.trackBar1); 88 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 89 | this.MaximizeBox = false; 90 | this.Name = "FormMain"; 91 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 92 | this.Text = "NAudio Pitchshift Example"; 93 | ((System.ComponentModel.ISupportInitialize)(this.trackBar1)).EndInit(); 94 | this.ResumeLayout(false); 95 | this.PerformLayout(); 96 | 97 | } 98 | 99 | #endregion 100 | 101 | private System.Windows.Forms.TrackBar trackBar1; 102 | private System.Windows.Forms.Label label1; 103 | private System.Windows.Forms.Button button1; 104 | private System.Windows.Forms.Button button2; 105 | } 106 | } 107 | 108 | -------------------------------------------------------------------------------- /SMBPitchShiftingSampleProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Diagnostics; 6 | using NAudio.Wave; 7 | 8 | /// 9 | /// Author: Freefall 10 | /// Date: 05.08.16 11 | /// Based on: the port of Stephan M. Bernsee´s pitch shifting class 12 | /// Port site: https://sites.google.com/site/mikescoderama/pitch-shifting 13 | /// Test application and github site: https://github.com/Freefall63/NAudio-Pitchshifter 14 | /// 15 | /// NOTE: I strongly advice to add a Limiter for post-processing. 16 | /// For my needs the FastAttackCompressor1175 provides acceptable results: 17 | /// https://github.com/Jiyuu/SkypeFX/blob/master/JSNet/FastAttackCompressor1175.cs 18 | /// 19 | /// UPDATE: Added a simple Limiter based on the pydirac implementation. 20 | /// https://github.com/echonest/remix/blob/master/external/pydirac225/source/Dirac_LE.cpp 21 | /// 22 | public class SMBPitchShiftingSampleProvider : ISampleProvider 23 | { 24 | //Shifter objects 25 | private ISampleProvider SourceStream = null; 26 | private WaveFormat WFormat = null; 27 | private float Pitch = 1f; 28 | private int _FFTSize; 29 | private long _osamp; 30 | private SMBPitchShifter ShifterLeft = new SMBPitchShifter(); 31 | private SMBPitchShifter ShifterRight = new SMBPitchShifter(); 32 | 33 | //Limiter constants 34 | const float LIM_THRESH = 0.95f; 35 | const float LIM_RANGE = (1f - LIM_THRESH); 36 | const float M_PI_2 = (float)(Math.PI / 2); 37 | 38 | public SMBPitchShiftingSampleProvider(ISampleProvider SourceProvider) 39 | : this(SourceProvider, 4096, 4L, 1f) { } 40 | 41 | public SMBPitchShiftingSampleProvider(ISampleProvider SourceProvider, int FFTSize, long osamp, float InitialPitch) 42 | { 43 | SourceStream = SourceProvider; 44 | WFormat = SourceProvider.WaveFormat; 45 | _FFTSize = FFTSize; 46 | _osamp = osamp; 47 | PitchFactor = InitialPitch; 48 | } 49 | 50 | public int Read(float[] buffer, int offset, int count) 51 | { 52 | int SampRead = SourceStream.Read(buffer, offset, count); 53 | if (Pitch == 1f) 54 | //Nothing to do. 55 | return SampRead; 56 | if (WFormat.Channels == 1) { 57 | float[] Mono = new float[SampRead]; 58 | int index = 0; 59 | for (int sample = offset; sample <= SampRead + offset - 1; sample++) { 60 | Mono[index] = buffer[sample]; 61 | index += 1; 62 | } 63 | ShifterLeft.PitchShift(Pitch, SampRead, _FFTSize, _osamp, WFormat.SampleRate, Mono); 64 | index = 0; 65 | for (int sample = offset; sample <= SampRead + offset - 1; sample++) { 66 | buffer[sample] = Limiter(Mono[index]); 67 | index += 1; 68 | } 69 | return SampRead; 70 | } else if (WFormat.Channels == 2) { 71 | float[] Left = new float[(SampRead >> 1)]; 72 | float[] Right = new float[(SampRead >> 1)]; 73 | int index = 0; 74 | for (int sample = offset; sample <= SampRead + offset - 1; sample += 2) { 75 | Left[index] = buffer[sample]; 76 | Right[index] = buffer[sample + 1]; 77 | index += 1; 78 | } 79 | ShifterLeft.PitchShift(Pitch, SampRead >> 1, _FFTSize, _osamp, WFormat.SampleRate, Left); 80 | ShifterRight.PitchShift(Pitch, SampRead >> 1, _FFTSize, _osamp, WFormat.SampleRate, Right); 81 | index = 0; 82 | for (int sample = offset; sample <= SampRead + offset - 1; sample += 2) { 83 | buffer[sample] = Limiter(Left[index]); 84 | buffer[sample + 1] = Limiter(Right[index]); 85 | index += 1; 86 | } 87 | return SampRead; 88 | } else { 89 | throw new Exception("Shifting of more than 2 channels is currently not supported."); 90 | } 91 | } 92 | 93 | public NAudio.Wave.WaveFormat WaveFormat { 94 | get { return WFormat; } 95 | } 96 | 97 | public float PitchFactor { 98 | get { return Pitch; } 99 | set { 100 | Pitch = value; 101 | } 102 | } 103 | 104 | private float Limiter(float Sample) 105 | { 106 | float res = 0f; 107 | if ((LIM_THRESH < Sample)) { 108 | res = (Sample - LIM_THRESH) / LIM_RANGE; 109 | res = (float)((Math.Atan(res) / M_PI_2) * LIM_RANGE + LIM_THRESH); 110 | } else if ((Sample < -LIM_THRESH)) { 111 | res = -(Sample + LIM_THRESH) / LIM_RANGE; 112 | res = -(float)((Math.Atan(res) / M_PI_2) * LIM_RANGE + LIM_THRESH); 113 | } else { 114 | res = Sample; 115 | } 116 | return res; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /FormMain.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /SMBPitchShifter.cs: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * 3 | * NAME: PitchShift.cs 4 | * VERSION: 1.0 5 | * HOME URL: http://www.dspdimension.com 6 | * KNOWN BUGS: none 7 | * 8 | * SYNOPSIS: Routine for doing pitch shifting while maintaining 9 | * duration using the Short Time Fourier Transform. 10 | * 11 | * DESCRIPTION: The routine takes a pitchShift factor value which is between 0.5 12 | * (one octave down) and 2. (one octave up). A value of exactly 1 does not change 13 | * the pitch. numSampsToProcess tells the routine how many samples in indata[0... 14 | * numSampsToProcess-1] should be pitch shifted and moved to outdata[0 ... 15 | * numSampsToProcess-1]. The two buffers can be identical (ie. it can process the 16 | * data in-place). fftFrameSize defines the FFT frame size used for the 17 | * processing. Typical values are 1024, 2048 and 4096. It may be any value <= 18 | * MAX_FRAME_LENGTH but it MUST be a power of 2. osamp is the STFT 19 | * oversampling factor which also determines the overlap between adjacent STFT 20 | * frames. It should at least be 4 for moderate scaling ratios. A value of 32 is 21 | * recommended for best quality. sampleRate takes the sample rate for the signal 22 | * in unit Hz, ie. 44100 for 44.1 kHz audio. The data passed to the routine in 23 | * indata[] should be in the range [-1.0, 1.0), which is also the output range 24 | * for the data, make sure you scale the data accordingly (for 16bit signed integers 25 | * you would have to divide (and multiply) by 32768). 26 | * 27 | * COPYRIGHT 1999-2006 Stephan M. Bernsee 28 | * 29 | * The Wide Open License (WOL) 30 | * 31 | * Permission to use, copy, modify, distribute and sell this software and its 32 | * documentation for any purpose is hereby granted without fee, provided that 33 | * the above copyright notice and this license appear in all source copies. 34 | * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF 35 | * ANY KIND. See http://www.dspguru.com/wol.htm for more information. 36 | * 37 | *****************************************************************************/ 38 | 39 | /**************************************************************************** 40 | * 41 | * This code was converted to C# by Michael Knight 42 | * madmik3 at gmail dot com. 43 | * http://sites.google.com/site/mikescoderama/ 44 | * 45 | *****************************************************************************/ 46 | 47 | using System; 48 | using System.Collections.Generic; 49 | using System.Text; 50 | 51 | public class SMBPitchShifter 52 | { 53 | 54 | private static int MAX_FRAME_LENGTH = 16000; 55 | private float[] gInFIFO = new float[MAX_FRAME_LENGTH]; 56 | private float[] gOutFIFO = new float[MAX_FRAME_LENGTH]; 57 | private float[] gFFTworksp = new float[2 * MAX_FRAME_LENGTH]; 58 | private float[] gLastPhase = new float[MAX_FRAME_LENGTH / 2 + 1]; 59 | private float[] gSumPhase = new float[MAX_FRAME_LENGTH / 2 + 1]; 60 | private float[] gOutputAccum = new float[2 * MAX_FRAME_LENGTH]; 61 | private float[] gAnaFreq = new float[MAX_FRAME_LENGTH]; 62 | private float[] gAnaMagn = new float[MAX_FRAME_LENGTH]; 63 | private float[] gSynFreq = new float[MAX_FRAME_LENGTH]; 64 | private float[] gSynMagn = new float[MAX_FRAME_LENGTH]; 65 | private long gRover, gInit; 66 | 67 | public void PitchShift(float pitchShift, long numSampsToProcess, 68 | float sampleRate, float[] indata) 69 | { 70 | PitchShift(pitchShift, numSampsToProcess, (long)2048, (long)10, sampleRate, indata); 71 | } 72 | public void PitchShift(float pitchShift, long numSampsToProcess, long fftFrameSize, 73 | long osamp, float sampleRate, float[] indata) 74 | { 75 | double magn, phase, tmp, window, real, imag; 76 | double freqPerBin, expct; 77 | long i, k, qpd, index, inFifoLatency, stepSize, fftFrameSize2; 78 | 79 | 80 | float[] outdata = indata; 81 | /* set up some handy variables */ 82 | fftFrameSize2 = fftFrameSize / 2; 83 | stepSize = fftFrameSize / osamp; 84 | freqPerBin = sampleRate / (double)fftFrameSize; 85 | expct = 2.0 * Math.PI * (double)stepSize / (double)fftFrameSize; 86 | inFifoLatency = fftFrameSize - stepSize; 87 | if (gRover == 0) gRover = inFifoLatency; 88 | 89 | 90 | /* main processing loop */ 91 | for (i = 0; i < numSampsToProcess; i++) 92 | { 93 | 94 | /* As long as we have not yet collected enough data just read in */ 95 | gInFIFO[gRover] = indata[i]; 96 | outdata[i] = gOutFIFO[gRover - inFifoLatency]; 97 | gRover++; 98 | 99 | /* now we have enough data for processing */ 100 | if (gRover >= fftFrameSize) 101 | { 102 | gRover = inFifoLatency; 103 | 104 | /* do windowing and re,im interleave */ 105 | for (k = 0; k < fftFrameSize; k++) 106 | { 107 | window = -.5 * Math.Cos(2.0 * Math.PI * (double)k / (double)fftFrameSize) + .5; 108 | gFFTworksp[2 * k] = (float)(gInFIFO[k] * window); 109 | gFFTworksp[2 * k + 1] = 0.0F; 110 | } 111 | 112 | 113 | /* ***************** ANALYSIS ******************* */ 114 | /* do transform */ 115 | ShortTimeFourierTransform(gFFTworksp, fftFrameSize, -1); 116 | 117 | /* this is the analysis step */ 118 | for (k = 0; k <= fftFrameSize2; k++) 119 | { 120 | 121 | /* de-interlace FFT buffer */ 122 | real = gFFTworksp[2 * k]; 123 | imag = gFFTworksp[2 * k + 1]; 124 | 125 | /* compute magnitude and phase */ 126 | magn = 2.0 * Math.Sqrt(real * real + imag * imag); 127 | phase = Math.Atan2(imag, real); 128 | 129 | /* compute phase difference */ 130 | tmp = phase - gLastPhase[k]; 131 | gLastPhase[k] = (float)phase; 132 | 133 | /* subtract expected phase difference */ 134 | tmp -= (double)k * expct; 135 | 136 | /* map delta phase into +/- Pi interval */ 137 | qpd = (long)(tmp / Math.PI); 138 | if (qpd >= 0) qpd += qpd & 1; 139 | else qpd -= qpd & 1; 140 | tmp -= Math.PI * (double)qpd; 141 | 142 | /* get deviation from bin frequency from the +/- Pi interval */ 143 | tmp = osamp * tmp / (2.0 * Math.PI); 144 | 145 | /* compute the k-th partials' true frequency */ 146 | tmp = (double)k * freqPerBin + tmp * freqPerBin; 147 | 148 | /* store magnitude and true frequency in analysis arrays */ 149 | gAnaMagn[k] = (float)magn; 150 | gAnaFreq[k] = (float)tmp; 151 | 152 | } 153 | 154 | /* ***************** PROCESSING ******************* */ 155 | /* this does the actual pitch shifting */ 156 | for (int zero = 0; zero < fftFrameSize; zero++) 157 | { 158 | gSynMagn[zero] = 0; 159 | gSynFreq[zero] = 0; 160 | } 161 | 162 | for (k = 0; k <= fftFrameSize2; k++) 163 | { 164 | index = (long)(k * pitchShift); 165 | if (index <= fftFrameSize2) 166 | { 167 | gSynMagn[index] += gAnaMagn[k]; 168 | gSynFreq[index] = gAnaFreq[k] * pitchShift; 169 | } 170 | } 171 | 172 | /* ***************** SYNTHESIS ******************* */ 173 | /* this is the synthesis step */ 174 | for (k = 0; k <= fftFrameSize2; k++) 175 | { 176 | 177 | /* get magnitude and true frequency from synthesis arrays */ 178 | magn = gSynMagn[k]; 179 | tmp = gSynFreq[k]; 180 | 181 | /* subtract bin mid frequency */ 182 | tmp -= (double)k * freqPerBin; 183 | 184 | /* get bin deviation from freq deviation */ 185 | tmp /= freqPerBin; 186 | 187 | /* take osamp into account */ 188 | tmp = 2.0 * Math.PI * tmp / osamp; 189 | 190 | /* add the overlap phase advance back in */ 191 | tmp += (double)k * expct; 192 | 193 | /* accumulate delta phase to get bin phase */ 194 | gSumPhase[k] += (float)tmp; 195 | phase = gSumPhase[k]; 196 | 197 | /* get real and imag part and re-interleave */ 198 | gFFTworksp[2 * k] = (float)(magn * Math.Cos(phase)); 199 | gFFTworksp[2 * k + 1] = (float)(magn * Math.Sin(phase)); 200 | } 201 | 202 | /* zero negative frequencies */ 203 | for (k = fftFrameSize + 2; k < 2 * fftFrameSize; k++) gFFTworksp[k] = 0.0F; 204 | 205 | /* do inverse transform */ 206 | ShortTimeFourierTransform(gFFTworksp, fftFrameSize, 1); 207 | 208 | /* do windowing and add to output accumulator */ 209 | for (k = 0; k < fftFrameSize; k++) 210 | { 211 | window = -.5 * Math.Cos(2.0 * Math.PI * (double)k / (double)fftFrameSize) + .5; 212 | gOutputAccum[k] += (float)(2.0 * window * gFFTworksp[2 * k] / (fftFrameSize2 * osamp)); 213 | } 214 | for (k = 0; k < stepSize; k++) gOutFIFO[k] = gOutputAccum[k]; 215 | 216 | /* shift accumulator */ 217 | //memmove(gOutputAccum, gOutputAccum + stepSize, fftFrameSize * sizeof(float)); 218 | for (k = 0; k < fftFrameSize; k++) 219 | { 220 | gOutputAccum[k] = gOutputAccum[k + stepSize]; 221 | } 222 | 223 | /* move input FIFO */ 224 | for (k = 0; k < inFifoLatency; k++) gInFIFO[k] = gInFIFO[k + stepSize]; 225 | } 226 | } 227 | } 228 | 229 | public void ShortTimeFourierTransform(float[] fftBuffer, long fftFrameSize, long sign) 230 | { 231 | float wr, wi, arg, temp; 232 | float tr, ti, ur, ui; 233 | long i, bitm, j, le, le2, k; 234 | 235 | for (i = 2; i < 2 * fftFrameSize - 2; i += 2) 236 | { 237 | for (bitm = 2, j = 0; bitm < 2 * fftFrameSize; bitm <<= 1) 238 | { 239 | if ((i & bitm) != 0) j++; 240 | j <<= 1; 241 | } 242 | if (i < j) 243 | { 244 | temp = fftBuffer[i]; 245 | fftBuffer[i] = fftBuffer[j]; 246 | fftBuffer[j] = temp; 247 | temp = fftBuffer[i + 1]; 248 | fftBuffer[i + 1] = fftBuffer[j + 1]; 249 | fftBuffer[j + 1] = temp; 250 | } 251 | } 252 | long max = (long)(Math.Log(fftFrameSize) / Math.Log(2.0) + .5); 253 | for (k = 0, le = 2; k < max; k++) 254 | { 255 | le <<= 1; 256 | le2 = le >> 1; 257 | ur = 1.0F; 258 | ui = 0.0F; 259 | arg = (float)Math.PI / (le2 >> 1); 260 | wr = (float)Math.Cos(arg); 261 | wi = (float)(sign * Math.Sin(arg)); 262 | for (j = 0; j < le2; j += 2) 263 | { 264 | 265 | for (i = j; i < 2 * fftFrameSize; i += le) 266 | { 267 | tr = fftBuffer[i + le2] * ur - fftBuffer[i + le2 + 1] * ui; 268 | ti = fftBuffer[i + le2] * ui + fftBuffer[i + le2 + 1] * ur; 269 | fftBuffer[i + le2] = fftBuffer[i] - tr; 270 | fftBuffer[i + le2 + 1] = fftBuffer[i + 1] - ti; 271 | fftBuffer[i] += tr; 272 | fftBuffer[i + 1] += ti; 273 | 274 | } 275 | tr = ur * wr - ui * wi; 276 | ui = ur * wi + ui * wr; 277 | ur = tr; 278 | } 279 | } 280 | } 281 | } 282 | --------------------------------------------------------------------------------