Merge branch 'RecordImprovements' into develop
Conflicts: Vocaluxe/Lib/Sound/CPortAudioHandle.cs Vocaluxe/Lib/Sound/Record/CBuffer.cs
This commit is contained in:
commit
9fe9455eaf
52 files changed
+7169
-497
No files matched your search
+5
-8
@@ -14,13 +14,13 @@ Output/Songs
|
||||
/*.ctl
|
||||
/Output/Registration.xml
|
||||
/Vocaluxe.suo
|
||||
/Vocaluxe/Vocaluxe.csproj.user
|
||||
*.csproj.user
|
||||
*.vcxproj.user
|
||||
/Output/Config.xml
|
||||
*.ipch
|
||||
*.log
|
||||
/Output/Playlists/*.xml
|
||||
/Output/x64/gstreamer/GstreamerHelper.dll
|
||||
/Output/x86/gstreamer/GstreamerHelper.dll
|
||||
GstreamerHelper.dll
|
||||
*.tlog
|
||||
*.suo
|
||||
*.obj
|
||||
@@ -39,11 +39,7 @@ Output/Songs
|
||||
*.Cache
|
||||
/GstreamerHelper/bin/*
|
||||
/VocaluxeLib/bin
|
||||
/ClientServerLib/ClientServerLib/ClientServerLib.csproj.user
|
||||
/ClientServerLib/ClientServerLib/bin/Debug/ClientServerLib.dll
|
||||
/ClientServerLib/ClientTest/bin/Debug/ClientServerLib.dll
|
||||
/ClientServerLib/ClientTest/bin/Release/ClientServerLib.dll
|
||||
/ClientServerLib/ClientServerLib/bin/ClientServerLib.dll
|
||||
ClientServerLib.dll
|
||||
ClientTest.exe
|
||||
*.vshost.*
|
||||
*.userprefs
|
||||
@@ -63,3 +59,4 @@ Output/Vocaluxe_x64.exe
|
||||
/Output/VocaluxeServerConfig.exe.config
|
||||
/PhoneGap/
|
||||
/Output/Profiles
|
||||
PitchTracker.dll
|
||||
@@ -0,0 +1,180 @@
|
||||
/**********************************************************************
|
||||
|
||||
FFT.cpp
|
||||
|
||||
Dominic Mazzoni
|
||||
|
||||
September 2000
|
||||
|
||||
*******************************************************************//*!
|
||||
|
||||
\file FFT.cpp
|
||||
\brief Fast Fourier Transform routines.
|
||||
|
||||
This file contains a few FFT routines, including a real-FFT
|
||||
routine that is almost twice as fast as a normal complex FFT,
|
||||
and a power spectrum routine when you know you don't care
|
||||
about phase information.
|
||||
|
||||
Some of this code was based on a free implementation of an FFT
|
||||
by Don Cross, available on the web at:
|
||||
|
||||
http://www.intersrv.com/~dcross/fft.html
|
||||
|
||||
The basic algorithm for his code was based on Numerican Recipes
|
||||
in Fortran. I optimized his code further by reducing array
|
||||
accesses, caching the bit reversal table, and eliminating
|
||||
float-to-double conversions, and I added the routines to
|
||||
calculate a real FFT and a real power spectrum.
|
||||
|
||||
*//*******************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "FFT.h"
|
||||
|
||||
#include "RealFFTf.h"
|
||||
|
||||
void DeinitFFT()
|
||||
{
|
||||
// Deallocate any unused RealFFTf tables
|
||||
CleanupFFT();
|
||||
}
|
||||
|
||||
/*
|
||||
* Real Fast Fourier Transform
|
||||
*
|
||||
*/
|
||||
void RealFFT(int NumSamples, float *RealIn, float *RealOut, float *ImagOut)
|
||||
{
|
||||
// Remap to RealFFTf() function
|
||||
int i;
|
||||
HFFT hFFT = GetFFT(NumSamples);
|
||||
float *pFFT = new float[NumSamples];
|
||||
// Copy the data into the processing buffer
|
||||
for(i=0; i<NumSamples; i++)
|
||||
pFFT[i] = RealIn[i];
|
||||
|
||||
// Perform the FFT
|
||||
RealFFTf(pFFT, hFFT);
|
||||
|
||||
// Copy the data into the real and imaginary outputs
|
||||
for(i=1;i<(NumSamples/2);i++) {
|
||||
RealOut[i]=pFFT[hFFT->BitReversed[i] ];
|
||||
ImagOut[i]=pFFT[hFFT->BitReversed[i]+1];
|
||||
}
|
||||
// Handle the (real-only) DC and Fs/2 bins
|
||||
RealOut[0] = pFFT[0];
|
||||
RealOut[i] = pFFT[1];
|
||||
ImagOut[0] = ImagOut[i] = 0;
|
||||
// Fill in the upper half using symmetry properties
|
||||
for(i++ ; i<NumSamples; i++) {
|
||||
RealOut[i] = RealOut[NumSamples-i];
|
||||
ImagOut[i] = -ImagOut[NumSamples-i];
|
||||
}
|
||||
delete [] pFFT;
|
||||
ReleaseFFT(hFFT);
|
||||
}
|
||||
|
||||
/*
|
||||
* InverseRealFFT
|
||||
*
|
||||
* This function computes the inverse of RealFFT, above.
|
||||
* The RealIn and ImagIn is assumed to be conjugate-symmetric
|
||||
* and as a result the output is purely real.
|
||||
* Only the first half of RealIn and ImagIn are used due to this
|
||||
* symmetry assumption.
|
||||
*/
|
||||
void InverseRealFFT(int NumSamples, float *RealIn, float *ImagIn, float *RealOut)
|
||||
{
|
||||
// Remap to RealFFTf() function
|
||||
int i;
|
||||
HFFT hFFT = GetFFT(NumSamples);
|
||||
float *pFFT = new float[NumSamples];
|
||||
// Copy the data into the processing buffer
|
||||
for(i=0; i<(NumSamples/2); i++)
|
||||
pFFT[2*i ] = RealIn[i];
|
||||
if(ImagIn == NULL) {
|
||||
for(i=0; i<(NumSamples/2); i++)
|
||||
pFFT[2*i+1] = 0;
|
||||
} else {
|
||||
for(i=0; i<(NumSamples/2); i++)
|
||||
pFFT[2*i+1] = ImagIn[i];
|
||||
}
|
||||
// Put the fs/2 component in the imaginary part of the DC bin
|
||||
pFFT[1] = RealIn[i];
|
||||
|
||||
// Perform the FFT
|
||||
InverseRealFFTf(pFFT, hFFT);
|
||||
|
||||
// Copy the data to the (purely real) output buffer
|
||||
ReorderToTime(hFFT, pFFT, RealOut);
|
||||
|
||||
delete [] pFFT;
|
||||
ReleaseFFT(hFFT);
|
||||
}
|
||||
|
||||
void RealInverseRealFFT(int NumSamples, float *In, float *Out){
|
||||
// Remap to RealFFTf() function
|
||||
int i;
|
||||
HFFT hFFT = GetFFT(NumSamples);
|
||||
float *pFFT = new float[NumSamples];
|
||||
// Copy the data into the processing buffer
|
||||
pFFT[0] = In[0];
|
||||
for(i=1; i<NumSamples/2; i++){
|
||||
pFFT[i] = In[i];
|
||||
pFFT[NumSamples - i] = In[i];
|
||||
}
|
||||
pFFT[NumSamples/2] = In[NumSamples/2];
|
||||
|
||||
// Perform the FFT
|
||||
RealFFTf(pFFT, hFFT);
|
||||
|
||||
// Copy the data into the output
|
||||
for(i=1;i<NumSamples/2;i++) {
|
||||
Out[i]=pFFT[hFFT->BitReversed[i]];
|
||||
}
|
||||
// Handle the (real-only) DC
|
||||
Out[0] = pFFT[0];
|
||||
delete [] pFFT;
|
||||
ReleaseFFT(hFFT);
|
||||
}
|
||||
|
||||
/*
|
||||
* PowerSpectrum
|
||||
*
|
||||
* This function computes the same as RealFFT, above, but
|
||||
* adds the squares of the real and imaginary part of each
|
||||
* coefficient, extracting the power and throwing away the
|
||||
* phase.
|
||||
*
|
||||
* For speed, it does not call RealFFT, but duplicates some
|
||||
* of its code.
|
||||
*/
|
||||
|
||||
void PowerSpectrum(int NumSamples, float *In, float *Out)
|
||||
{
|
||||
// Remap to RealFFTf() function
|
||||
int i;
|
||||
HFFT hFFT = GetFFT(NumSamples);
|
||||
float *pFFT = new float[NumSamples];
|
||||
// Copy the data into the processing buffer
|
||||
for(i=0; i<NumSamples; i++)
|
||||
pFFT[i] = In[i];
|
||||
|
||||
// Perform the FFT
|
||||
RealFFTf(pFFT, hFFT);
|
||||
|
||||
// Copy the data into the real and imaginary outputs
|
||||
for(i=1;i<NumSamples/2;i++) {
|
||||
Out[i]= (pFFT[hFFT->BitReversed[i] ]*pFFT[hFFT->BitReversed[i] ])
|
||||
+ (pFFT[hFFT->BitReversed[i]+1]*pFFT[hFFT->BitReversed[i]+1]);
|
||||
}
|
||||
// Handle the (real-only) DC and Fs/2 bins
|
||||
Out[0] = pFFT[0]*pFFT[0];
|
||||
Out[i] = pFFT[1]*pFFT[1];
|
||||
delete [] pFFT;
|
||||
ReleaseFFT(hFFT);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**********************************************************************
|
||||
|
||||
FFT.h
|
||||
|
||||
Dominic Mazzoni
|
||||
|
||||
September 2000
|
||||
|
||||
This file contains a few FFT routines, including a real-FFT
|
||||
routine that is almost twice as fast as a normal complex FFT,
|
||||
and a power spectrum routine which is more convenient when
|
||||
you know you don't care about phase information. It now also
|
||||
contains a few basic windowing functions.
|
||||
|
||||
Some of this code was based on a free implementation of an FFT
|
||||
by Don Cross, available on the web at:
|
||||
|
||||
http://www.intersrv.com/~dcross/fft.html
|
||||
|
||||
The basic algorithm for his code was based on Numerical Recipes
|
||||
in Fortran. I optimized his code further by reducing array
|
||||
accesses, caching the bit reversal table, and eliminating
|
||||
float-to-double conversions, and I added the routines to
|
||||
calculate a real FFT and a real power spectrum.
|
||||
|
||||
Note: all of these routines use single-precision floats.
|
||||
I have found that in practice, floats work well until you
|
||||
get above 8192 samples. If you need to do a larger FFT,
|
||||
you need to use doubles.
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
/*
|
||||
* This is the function you will use the most often.
|
||||
* Given an array of floats, this will compute the power
|
||||
* spectrum by doing a Real FFT and then computing the
|
||||
* sum of the squares of the real and imaginary parts.
|
||||
* Note that the output array is half the length of the
|
||||
* input array, and that NumSamples must be a power of two.
|
||||
*/
|
||||
|
||||
void PowerSpectrum(int NumSamples, float *In, float *Out);
|
||||
|
||||
/*
|
||||
* Computes an FFT when the input data is real but you still
|
||||
* want complex data as output. The output arrays are the
|
||||
* same length as the input, but will be conjugate-symmetric
|
||||
* NumSamples must be a power of two.
|
||||
*/
|
||||
|
||||
void RealFFT(int NumSamples,
|
||||
float *RealIn, float *RealOut, float *ImagOut);
|
||||
|
||||
/*
|
||||
* Computes an Inverse FFT when the input data is conjugate symmetric
|
||||
* so the output is purely real. NumSamples must be a power of
|
||||
* two.
|
||||
* Requires: EXPERIMENTAL_USE_REALFFTF
|
||||
*/
|
||||
void InverseRealFFT(int NumSamples,
|
||||
float *RealIn, float *ImagIn, float *RealOut);
|
||||
|
||||
void RealInverseRealFFT(int NumSamples, float *In, float *Out);
|
||||
|
||||
void DeinitFFT();
|
||||
@@ -0,0 +1,299 @@
|
||||
Code in this folder is based on Code from Audacity.
|
||||
License of that code is below:
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Note: Audacity is distributed under the terms of the GNU GPL.
|
||||
This includes everything in the source code distribution
|
||||
except code in the lib-src directory. Code in the lib-src
|
||||
directory may be released under a different license
|
||||
(which is GPL-compatible). For a summary, see the
|
||||
README.txt file, and for specific details, see the license
|
||||
information inside each subdirectory of lib-src.
|
||||
|
||||
Documentation is distributed under the Creative Commons license.
|
||||
Please see here for that:
|
||||
http://creativecommons.org/licenses/by/3.0/legalcode
|
||||
|
||||
The full text of the GNU GPL follows:
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Program: REALFFTF.C
|
||||
* Author: Philip Van Baren
|
||||
* Date: 2 September 1993
|
||||
*
|
||||
* Description: These routines perform an FFT on real data to get a conjugate-symmetric
|
||||
* output, and an inverse FFT on conjugate-symmetric input to get a real
|
||||
* output sequence.
|
||||
*
|
||||
* This code is for floating point data.
|
||||
*
|
||||
* Modified 8/19/1998 by Philip Van Baren
|
||||
* - made the InitializeFFT and EndFFT routines take a structure
|
||||
* holding the length and pointers to the BitReversed and SinTable
|
||||
* tables.
|
||||
* Modified 5/23/2009 by Philip Van Baren
|
||||
* - Added GetFFT and ReleaseFFT routines to retain common SinTable
|
||||
* and BitReversed tables so they don't need to be reallocated
|
||||
* and recomputed on every call.
|
||||
* - Added Reorder* functions to undo the bit-reversal
|
||||
*
|
||||
* Copyright (C) 2009 Philip VanBaren
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "RealFFTf.h"
|
||||
#ifdef EXPERIMENTAL_EQ_SSE_THREADED
|
||||
#include "RealFFTf48x.h"
|
||||
#endif
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846 /* pi */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Initialize the Sine table and Twiddle pointers (bit-reversed pointers)
|
||||
* for the FFT routine.
|
||||
*/
|
||||
HFFT InitializeFFT(int fftlen)
|
||||
{
|
||||
int i;
|
||||
int temp;
|
||||
int mask;
|
||||
HFFT h;
|
||||
|
||||
if((h=(HFFT)malloc(sizeof(FFTParam)))==NULL)
|
||||
{
|
||||
fprintf(stderr,"Error allocating memory for FFT\n");
|
||||
exit(8);
|
||||
}
|
||||
/*
|
||||
* FFT size is only half the number of data points
|
||||
* The full FFT output can be reconstructed from this FFT's output.
|
||||
* (This optimization can be made since the data is real.)
|
||||
*/
|
||||
h->Points = fftlen/2;
|
||||
|
||||
if((h->SinTable=(fft_type *)malloc(2*h->Points*sizeof(fft_type)))==NULL)
|
||||
{
|
||||
fprintf(stderr,"Error allocating memory for Sine table.\n");
|
||||
exit(8);
|
||||
}
|
||||
|
||||
if((h->BitReversed=(int *)malloc(h->Points*sizeof(int)))==NULL)
|
||||
{
|
||||
fprintf(stderr,"Error allocating memory for BitReversed.\n");
|
||||
exit(8);
|
||||
}
|
||||
|
||||
for(i=0;i<h->Points;i++)
|
||||
{
|
||||
temp=0;
|
||||
for(mask=h->Points/2;mask>0;mask >>= 1)
|
||||
temp=(temp >> 1) + (i&mask ? h->Points : 0);
|
||||
|
||||
h->BitReversed[i]=temp;
|
||||
}
|
||||
|
||||
for(i=0;i<h->Points;i++)
|
||||
{
|
||||
h->SinTable[h->BitReversed[i] ]=(fft_type)-sin(2*M_PI*i/(2*h->Points));
|
||||
h->SinTable[h->BitReversed[i]+1]=(fft_type)-cos(2*M_PI*i/(2*h->Points));
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free up the memory allotted for Sin table and Twiddle Pointers
|
||||
*/
|
||||
void EndFFT(HFFT h)
|
||||
{
|
||||
if(h->Points>0) {
|
||||
free(h->BitReversed);
|
||||
free(h->SinTable);
|
||||
}
|
||||
h->Points=0;
|
||||
free(h);
|
||||
}
|
||||
|
||||
#define MAX_HFFT 10
|
||||
static HFFT hFFTArray[MAX_HFFT] = { NULL };
|
||||
static int nFFTLockCount[MAX_HFFT] = { 0 };
|
||||
|
||||
/* Get a handle to the FFT tables of the desired length */
|
||||
/* This version keeps common tables rather than allocating a new table every time */
|
||||
HFFT GetFFT(int fftlen)
|
||||
{
|
||||
int h,n = fftlen/2;
|
||||
for(h=0; (h<MAX_HFFT) && (hFFTArray[h] != NULL) && (n != hFFTArray[h]->Points); h++);
|
||||
if(h<MAX_HFFT) {
|
||||
if(hFFTArray[h] == NULL) {
|
||||
hFFTArray[h] = InitializeFFT(fftlen);
|
||||
nFFTLockCount[h] = 0;
|
||||
}
|
||||
nFFTLockCount[h]++;
|
||||
return hFFTArray[h];
|
||||
} else {
|
||||
// All buffers used, so fall back to allocating a new set of tables
|
||||
return InitializeFFT(fftlen);;
|
||||
}
|
||||
}
|
||||
|
||||
/* Release a previously requested handle to the FFT tables */
|
||||
void ReleaseFFT(HFFT hFFT)
|
||||
{
|
||||
int h;
|
||||
for(h=0; (h<MAX_HFFT) && (hFFTArray[h] != hFFT); h++);
|
||||
if(h<MAX_HFFT) {
|
||||
nFFTLockCount[h]--;
|
||||
} else {
|
||||
EndFFT(hFFT);
|
||||
}
|
||||
}
|
||||
|
||||
/* Deallocate any unused FFT tables */
|
||||
void CleanupFFT()
|
||||
{
|
||||
int h;
|
||||
for(h=0; (h<MAX_HFFT); h++) {
|
||||
if((nFFTLockCount[h] <= 0) && (hFFTArray[h] != NULL)) {
|
||||
EndFFT(hFFTArray[h]);
|
||||
hFFTArray[h] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Forward FFT routine. Must call InitializeFFT(fftlen) first!
|
||||
*
|
||||
* Note: Output is BIT-REVERSED! so you must use the BitReversed to
|
||||
* get legible output, (i.e. Real_i = buffer[ h->BitReversed[i] ]
|
||||
* Imag_i = buffer[ h->BitReversed[i]+1 ] )
|
||||
* Input is in normal order.
|
||||
*
|
||||
* Output buffer[0] is the DC bin, and output buffer[1] is the Fs/2 bin
|
||||
* - this can be done because both values will always be real only
|
||||
* - this allows us to not have to allocate an extra complex value for the Fs/2 bin
|
||||
*
|
||||
* Note: The scaling on this is done according to the standard FFT definition,
|
||||
* so a unit amplitude DC signal will output an amplitude of (N)
|
||||
* (Older revisions would progressively scale the input, so the output
|
||||
* values would be similar in amplitude to the input values, which is
|
||||
* good when using fixed point arithmetic)
|
||||
*/
|
||||
void RealFFTf(fft_type *buffer,HFFT h)
|
||||
{
|
||||
fft_type *A,*B;
|
||||
fft_type *sptr;
|
||||
fft_type *endptr1,*endptr2;
|
||||
int *br1,*br2;
|
||||
fft_type HRplus,HRminus,HIplus,HIminus;
|
||||
fft_type v1,v2,sin,cos;
|
||||
|
||||
int ButterfliesPerGroup=h->Points/2;
|
||||
|
||||
/*
|
||||
* Butterfly:
|
||||
* Ain-----Aout
|
||||
* \ /
|
||||
* / \
|
||||
* Bin-----Bout
|
||||
*/
|
||||
|
||||
endptr1=buffer+h->Points*2;
|
||||
|
||||
while(ButterfliesPerGroup>0)
|
||||
{
|
||||
A=buffer;
|
||||
B=buffer+ButterfliesPerGroup*2;
|
||||
sptr=h->SinTable;
|
||||
|
||||
while(A<endptr1)
|
||||
{
|
||||
sin=*sptr;
|
||||
cos=*(sptr+1);
|
||||
endptr2=B;
|
||||
while(A<endptr2)
|
||||
{
|
||||
v1=*B*cos + *(B+1)*sin;
|
||||
v2=*B*sin - *(B+1)*cos;
|
||||
*B=(*A+v1);
|
||||
*(A++)=*(B++)-2*v1;
|
||||
*B=(*A-v2);
|
||||
*(A++)=*(B++)+2*v2;
|
||||
}
|
||||
A=B;
|
||||
B+=ButterfliesPerGroup*2;
|
||||
sptr+=2;
|
||||
}
|
||||
ButterfliesPerGroup >>= 1;
|
||||
}
|
||||
/* Massage output to get the output for a real input sequence. */
|
||||
br1=h->BitReversed+1;
|
||||
br2=h->BitReversed+h->Points-1;
|
||||
|
||||
while(br1<br2)
|
||||
{
|
||||
sin=h->SinTable[*br1];
|
||||
cos=h->SinTable[*br1+1];
|
||||
A=buffer+*br1;
|
||||
B=buffer+*br2;
|
||||
HRplus = (HRminus = *A - *B ) + (*B * 2);
|
||||
HIplus = (HIminus = *(A+1) - *(B+1)) + (*(B+1) * 2);
|
||||
v1 = (sin*HRminus - cos*HIplus);
|
||||
v2 = (cos*HRminus + sin*HIplus);
|
||||
*A = (HRplus + v1) * (fft_type)0.5;
|
||||
*B = *A - v1;
|
||||
*(A+1) = (HIminus + v2) * (fft_type)0.5;
|
||||
*(B+1) = *(A+1) - HIminus;
|
||||
|
||||
br1++;
|
||||
br2--;
|
||||
}
|
||||
/* Handle the center bin (just need a conjugate) */
|
||||
A=buffer+*br1+1;
|
||||
*A=-*A;
|
||||
/* Handle DC bin separately - and ignore the Fs/2 bin
|
||||
buffer[0]+=buffer[1];
|
||||
buffer[1]=(fft_type)0;*/
|
||||
/* Handle DC and Fs/2 bins separately */
|
||||
/* Put the Fs/2 value into the imaginary part of the DC bin */
|
||||
v1=buffer[0]-buffer[1];
|
||||
buffer[0]+=buffer[1];
|
||||
buffer[1]=v1;
|
||||
}
|
||||
|
||||
|
||||
/* Description: This routine performs an inverse FFT to real data.
|
||||
* This code is for floating point data.
|
||||
*
|
||||
* Note: Output is BIT-REVERSED! so you must use the BitReversed to
|
||||
* get legible output, (i.e. wave[2*i] = buffer[ BitReversed[i] ]
|
||||
* wave[2*i+1] = buffer[ BitReversed[i]+1 ] )
|
||||
* Input is in normal order, interleaved (real,imaginary) complex data
|
||||
* You must call InitializeFFT(fftlen) first to initialize some buffers!
|
||||
*
|
||||
* Input buffer[0] is the DC bin, and input buffer[1] is the Fs/2 bin
|
||||
* - this can be done because both values will always be real only
|
||||
* - this allows us to not have to allocate an extra complex value for the Fs/2 bin
|
||||
*
|
||||
* Note: The scaling on this is done according to the standard FFT definition,
|
||||
* so a unit amplitude DC signal will output an amplitude of (N)
|
||||
* (Older revisions would progressively scale the input, so the output
|
||||
* values would be similar in amplitude to the input values, which is
|
||||
* good when using fixed point arithmetic)
|
||||
*/
|
||||
void InverseRealFFTf(fft_type *buffer,HFFT h)
|
||||
{
|
||||
fft_type *A,*B;
|
||||
fft_type *sptr;
|
||||
fft_type *endptr1,*endptr2;
|
||||
int *br1;
|
||||
fft_type HRplus,HRminus,HIplus,HIminus;
|
||||
fft_type v1,v2,sin,cos;
|
||||
|
||||
int ButterfliesPerGroup=h->Points/2;
|
||||
|
||||
/* Massage input to get the input for a real output sequence. */
|
||||
A=buffer+2;
|
||||
B=buffer+h->Points*2-2;
|
||||
br1=h->BitReversed+1;
|
||||
while(A<B)
|
||||
{
|
||||
sin=h->SinTable[*br1];
|
||||
cos=h->SinTable[*br1+1];
|
||||
HRplus = (HRminus = *A - *B ) + (*B * 2);
|
||||
HIplus = (HIminus = *(A+1) - *(B+1)) + (*(B+1) * 2);
|
||||
v1 = (sin*HRminus + cos*HIplus);
|
||||
v2 = (cos*HRminus - sin*HIplus);
|
||||
*A = (HRplus + v1) * (fft_type)0.5;
|
||||
*B = *A - v1;
|
||||
*(A+1) = (HIminus - v2) * (fft_type)0.5;
|
||||
*(B+1) = *(A+1) - HIminus;
|
||||
|
||||
A+=2;
|
||||
B-=2;
|
||||
br1++;
|
||||
}
|
||||
/* Handle center bin (just need conjugate) */
|
||||
*(A+1)=-*(A+1);
|
||||
/* Handle DC bin separately - this ignores any Fs/2 component
|
||||
buffer[1]=buffer[0]=buffer[0]/2;*/
|
||||
/* Handle DC and Fs/2 bins specially */
|
||||
/* The DC bin is passed in as the real part of the DC complex value */
|
||||
/* The Fs/2 bin is passed in as the imaginary part of the DC complex value */
|
||||
/* (v1+v2) = buffer[0] == the DC component */
|
||||
/* (v1-v2) = buffer[1] == the Fs/2 component */
|
||||
v1=0.5f*(buffer[0]+buffer[1]);
|
||||
v2=0.5f*(buffer[0]-buffer[1]);
|
||||
buffer[0]=v1;
|
||||
buffer[1]=v2;
|
||||
|
||||
/*
|
||||
* Butterfly:
|
||||
* Ain-----Aout
|
||||
* \ /
|
||||
* / \
|
||||
* Bin-----Bout
|
||||
*/
|
||||
|
||||
endptr1=buffer+h->Points*2;
|
||||
|
||||
while(ButterfliesPerGroup>0)
|
||||
{
|
||||
A=buffer;
|
||||
B=buffer+ButterfliesPerGroup*2;
|
||||
sptr=h->SinTable;
|
||||
|
||||
while(A<endptr1)
|
||||
{
|
||||
sin=*(sptr++);
|
||||
cos=*(sptr++);
|
||||
endptr2=B;
|
||||
while(A<endptr2)
|
||||
{
|
||||
v1=*B*cos - *(B+1)*sin;
|
||||
v2=*B*sin + *(B+1)*cos;
|
||||
*B=(*A+v1)*(fft_type)0.5;
|
||||
*(A++)=*(B++)-v1;
|
||||
*B=(*A+v2)*(fft_type)0.5;
|
||||
*(A++)=*(B++)-v2;
|
||||
}
|
||||
A=B;
|
||||
B+=ButterfliesPerGroup*2;
|
||||
}
|
||||
ButterfliesPerGroup >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void ReorderToFreq(HFFT hFFT, fft_type *buffer, fft_type *RealOut, fft_type *ImagOut)
|
||||
{
|
||||
// Copy the data into the real and imaginary outputs
|
||||
for(int i=1;i<hFFT->Points;i++) {
|
||||
RealOut[i]=buffer[hFFT->BitReversed[i] ];
|
||||
ImagOut[i]=buffer[hFFT->BitReversed[i]+1];
|
||||
}
|
||||
RealOut[0] = buffer[0]; // DC component
|
||||
ImagOut[0] = 0;
|
||||
RealOut[hFFT->Points] = buffer[1]; // Fs/2 component
|
||||
ImagOut[hFFT->Points] = 0;
|
||||
}
|
||||
|
||||
void ReorderToTime(HFFT hFFT, fft_type *buffer, fft_type *TimeOut)
|
||||
{
|
||||
// Copy the data into the real outputs
|
||||
for(int i=0;i<hFFT->Points;i++) {
|
||||
TimeOut[i*2 ]=buffer[hFFT->BitReversed[i] ];
|
||||
TimeOut[i*2+1]=buffer[hFFT->BitReversed[i]+1];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef __realfftf_h
|
||||
#define __realfftf_h
|
||||
|
||||
#define fft_type float
|
||||
typedef struct FFTParamType {
|
||||
int *BitReversed;
|
||||
fft_type *SinTable;
|
||||
int Points;
|
||||
} FFTParam;
|
||||
#define HFFT FFTParam *
|
||||
|
||||
HFFT InitializeFFT(int);
|
||||
void EndFFT(HFFT);
|
||||
HFFT GetFFT(int);
|
||||
void ReleaseFFT(HFFT);
|
||||
void CleanupFFT();
|
||||
void RealFFTf(fft_type *,HFFT);
|
||||
void InverseRealFFTf(fft_type *,HFFT);
|
||||
void ReorderToTime(HFFT hFFT, fft_type *buffer, fft_type *TimeOut);
|
||||
void ReorderToFreq(HFFT hFFT, fft_type *buffer, fft_type *RealOut, fft_type *ImagOut);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "Helper.h"
|
||||
#include <stdlib.h>
|
||||
#include <cmath>
|
||||
|
||||
float* short2FloatArray(short* in, size_t len){
|
||||
const float maxShort = 32767.0f;
|
||||
|
||||
float* result = static_cast<float*>(malloc(len * sizeof(float)));
|
||||
float* curOut = result;
|
||||
for(size_t i=0; i<len; i++){
|
||||
*curOut = *in / maxShort;
|
||||
curOut++;
|
||||
in++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double* short2DoubleArray(short* in, size_t len, bool scale){
|
||||
const double maxShort = 32767.0;
|
||||
|
||||
double* result = static_cast<double*>(malloc(len * sizeof(double)));
|
||||
double* curOut = result;
|
||||
for(size_t i=0; i<len; i++){
|
||||
*curOut = *in;
|
||||
if(scale)
|
||||
*curOut /= maxShort;
|
||||
curOut++;
|
||||
in++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void freeFloatArray(float* floats){
|
||||
free(floats);
|
||||
}
|
||||
|
||||
void freeDoubleArray(double* floats){
|
||||
free(floats);
|
||||
}
|
||||
|
||||
double FreqToNote(double freq){
|
||||
return 12.0 * std::log(freq / 127.09) / std::log(2.0) + 27.5 - 16; // 16 = C2
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
float* short2FloatArray(short* in, size_t len);
|
||||
double* short2DoubleArray(short* in, size_t len, bool scale = true);
|
||||
void freeFloatArray(float* floats);
|
||||
void freeDoubleArray(double* floats);
|
||||
double FreqToNote(double freq);
|
||||
|
||||
/*
|
||||
namespace Native{
|
||||
namespace PitchTracking{
|
||||
CTone^ ConvertToneToManaged(Tone* tone){
|
||||
CTone^ result = gcnew CTone();
|
||||
result->Freq = tone->freq;
|
||||
result->DB = tone->db;
|
||||
result->Stabledb = tone->stabledb;
|
||||
result->Age = static_cast<int>(tone->age);
|
||||
result->NoteExact = 12.0 * std::log(tone->freq / 127.09) / std::log(2.0) + 27.5 - 16; // 16 = C2
|
||||
result->Note = static_cast<int>(std::round(result->NoteExact));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,203 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{54A48AF7-515C-4EDD-B395-2BFFA096050A}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>PitchTracker</RootNamespace>
|
||||
<ProjectName>PitchTracker</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v110_xp</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v110_xp</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v110_xp</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v110_xp</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>bin\$(Configuration).$(Platform)\</OutDir>
|
||||
<IntDir>obj\$(Configuration).$(Platform)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>bin\$(Configuration).$(Platform)\</OutDir>
|
||||
<IntDir>obj\$(Configuration).$(Platform)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>bin\$(Configuration).$(Platform)\</OutDir>
|
||||
<IntDir>obj\$(Configuration).$(Platform)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>bin\$(Configuration).$(Platform)\</OutDir>
|
||||
<IntDir>obj\$(Configuration).$(Platform)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<FloatingPointModel>Fast</FloatingPointModel>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>xcopy "$(OutDir)*.dll" "$(SolutionDir)Output\x64\" /I /Y</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<FloatingPointModel>Fast</FloatingPointModel>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>xcopy "$(OutDir)*.dll" "$(SolutionDir)Output\x86\" /I /Y</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
<FloatingPointModel>Fast</FloatingPointModel>
|
||||
<OmitFramePointers>false</OmitFramePointers>
|
||||
<OpenMPSupport>true</OpenMPSupport>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>xcopy "$(OutDir)*.dll" "$(SolutionDir)Output\x64\" /I /Y</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
<FloatingPointModel>Fast</FloatingPointModel>
|
||||
<OpenMPSupport>true</OpenMPSupport>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>xcopy "$(OutDir)*.dll" "$(SolutionDir)Output\x86\" /I /Y</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="compatibility.cpp" />
|
||||
<ClCompile Include="dywapitchtrack\dywapitchtrack.cpp" />
|
||||
<ClCompile Include="dywapitchtrack\ptDyWa.cpp" />
|
||||
<ClCompile Include="FFT\FFT.cpp" />
|
||||
<ClCompile Include="FFT\RealFFTf.cpp" />
|
||||
<ClCompile Include="Helper.cpp" />
|
||||
<ClCompile Include="performous\pitch.cc" />
|
||||
<ClCompile Include="ptAKF.cpp" />
|
||||
<ClCompile Include="PitchWrapper.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="compatibility.h" />
|
||||
<ClInclude Include="dywapitchtrack\dywapitchtrack.h" />
|
||||
<ClInclude Include="dywapitchtrack\ptDyWa.h" />
|
||||
<ClInclude Include="FFT\FFT.h" />
|
||||
<ClInclude Include="FFT\RealFFTf.h" />
|
||||
<ClInclude Include="Helper.h" />
|
||||
<ClInclude Include="performous\libda\fft.hpp" />
|
||||
<ClInclude Include="performous\libda\sample.hpp" />
|
||||
<ClInclude Include="ptAKF.h" />
|
||||
<ClInclude Include="PitchWrapper.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="performous\pitch.hh" />
|
||||
<None Include="performous\util.hh" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Quelldateien">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Headerdateien">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Ressourcendateien">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Quelldateien\performous">
|
||||
<UniqueIdentifier>{a47c9d33-78d2-453d-bb59-e5724cd85f67}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Headerdateien\performous">
|
||||
<UniqueIdentifier>{4c72400a-ff2c-4842-8c20-54deb5df606d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Headerdateien\performous\libda">
|
||||
<UniqueIdentifier>{d71782a0-47fc-4fe1-8569-0a0ce8fe9b45}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Quelldateien\dywapitchtrack">
|
||||
<UniqueIdentifier>{e8eab624-6cc3-42db-93d9-575aaeff06f7}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Headerdateien\dywapitchtrack">
|
||||
<UniqueIdentifier>{2a09cee1-51b7-4c24-aa9f-32bec53c3a36}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Quelldateien\FFT">
|
||||
<UniqueIdentifier>{498f64e2-ff83-41fd-be25-c613750f0e34}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Headerdateien\FFT">
|
||||
<UniqueIdentifier>{0791b62f-ef8d-40e2-b67e-a6d9dead8720}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="PitchWrapper.cpp">
|
||||
<Filter>Quelldateien</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="compatibility.cpp">
|
||||
<Filter>Quelldateien</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="performous\pitch.cc">
|
||||
<Filter>Quelldateien\performous</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ptAKF.cpp">
|
||||
<Filter>Quelldateien</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dywapitchtrack\ptDyWa.cpp">
|
||||
<Filter>Quelldateien\dywapitchtrack</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dywapitchtrack\dywapitchtrack.cpp">
|
||||
<Filter>Quelldateien\dywapitchtrack</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Helper.cpp">
|
||||
<Filter>Quelldateien</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FFT\FFT.cpp">
|
||||
<Filter>Quelldateien\FFT</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FFT\RealFFTf.cpp">
|
||||
<Filter>Quelldateien\FFT</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="compatibility.h">
|
||||
<Filter>Headerdateien</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PitchWrapper.h">
|
||||
<Filter>Headerdateien</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Helper.h">
|
||||
<Filter>Headerdateien</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="performous\libda\fft.hpp">
|
||||
<Filter>Headerdateien\performous\libda</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="performous\libda\sample.hpp">
|
||||
<Filter>Headerdateien\performous\libda</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ptAKF.h">
|
||||
<Filter>Headerdateien</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="dywapitchtrack\dywapitchtrack.h">
|
||||
<Filter>Headerdateien\dywapitchtrack</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="dywapitchtrack\ptDyWa.h">
|
||||
<Filter>Headerdateien\dywapitchtrack</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FFT\FFT.h">
|
||||
<Filter>Headerdateien\FFT</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FFT\RealFFTf.h">
|
||||
<Filter>Headerdateien\FFT</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="performous\pitch.hh">
|
||||
<Filter>Headerdateien\performous</Filter>
|
||||
</None>
|
||||
<None Include="performous\util.hh">
|
||||
<Filter>Headerdateien\performous</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,233 @@
|
||||
#include "PitchWrapper.h"
|
||||
#include "Helper.h"
|
||||
|
||||
|
||||
Analyzer* Analyzer_Create(unsigned step){
|
||||
return new Analyzer(44100, "", step);
|
||||
}
|
||||
|
||||
void Analyzer_Free(Analyzer* analyzer){
|
||||
if(analyzer)
|
||||
delete analyzer;
|
||||
}
|
||||
|
||||
void Analyzer_InputFloat(Analyzer* analyzer, float* data, int sampleCt){
|
||||
if(sampleCt == 0 || !analyzer)
|
||||
return;
|
||||
analyzer->input(data, data + sampleCt);
|
||||
}
|
||||
|
||||
void Analyzer_InputShort(Analyzer* analyzer, short* data, int sampleCt){
|
||||
if(sampleCt == 0 || !analyzer)
|
||||
return;
|
||||
float* dataFloat = short2FloatArray(data, sampleCt);
|
||||
analyzer->input(dataFloat, dataFloat + sampleCt);
|
||||
freeFloatArray(dataFloat);
|
||||
}
|
||||
|
||||
void Analyzer_InputByte(Analyzer* analyzer, char* data, int sampleCt){
|
||||
if(sampleCt <= 0 || !analyzer)
|
||||
return;
|
||||
float* dataFloat = short2FloatArray(static_cast<short*>(static_cast<void*>(data)), sampleCt);
|
||||
analyzer->input(dataFloat, dataFloat + sampleCt);
|
||||
freeFloatArray(dataFloat);
|
||||
}
|
||||
|
||||
void Analyzer_Process(Analyzer* analyzer){
|
||||
if(!analyzer)
|
||||
return;
|
||||
analyzer->process();
|
||||
}
|
||||
|
||||
float Analyzer_GetPeak(Analyzer* analyzer){
|
||||
if(!analyzer)
|
||||
return -999;
|
||||
return static_cast<float>(analyzer->getPeakRaw());
|
||||
}
|
||||
|
||||
double Analyzer_FindNote(Analyzer* analyzer, double minFreq, double maxFreq){
|
||||
if(!analyzer)
|
||||
return -1;
|
||||
const Tone* tone = analyzer->findTone(minFreq, maxFreq);
|
||||
return (tone == NULL) ? -1 : FreqToNote(tone->freq);
|
||||
}
|
||||
|
||||
bool Analyzer_OutputFloat(Analyzer* analyzer, float* data, int sampleCt, float rate){
|
||||
if(!analyzer)
|
||||
return false;
|
||||
return analyzer->output(data, data + sampleCt, rate);
|
||||
}
|
||||
|
||||
PtAKF* PtAKF_Create(unsigned step){
|
||||
return new PtAKF(step);
|
||||
}
|
||||
|
||||
void PtAKF_Free(PtAKF* analyzer){
|
||||
if(analyzer)
|
||||
delete analyzer;
|
||||
}
|
||||
|
||||
int PtAKF_GetNumHalfTones(){
|
||||
return PtAKF::GetNumHalfTones();
|
||||
}
|
||||
|
||||
void PtAKF_SetVolumeThreshold(PtAKF* analyzer, float threshold){
|
||||
if(!analyzer)
|
||||
return;
|
||||
analyzer->SetVolumeThreshold(threshold);
|
||||
}
|
||||
|
||||
float PtAKF_GetVolumeThreshold(PtAKF* analyzer){
|
||||
if(!analyzer)
|
||||
return 0.f;
|
||||
return analyzer->GetVolumeThreshold();
|
||||
}
|
||||
|
||||
void PtAKF_InputByte(PtAKF* analyzer, char* data, int sampleCt){
|
||||
if(sampleCt <= 0 || !analyzer)
|
||||
return;
|
||||
//short* dataShort = static_cast<short*>(static_cast<void*>(data));
|
||||
float* dataFloat = short2FloatArray(static_cast<short*>(static_cast<void*>(data)), sampleCt);
|
||||
analyzer->input(dataFloat, dataFloat + sampleCt);
|
||||
freeFloatArray(dataFloat);
|
||||
|
||||
//analyzer->input(dataShort, dataShort + sampleCt);
|
||||
}
|
||||
|
||||
int PtAKF_GetNote(PtAKF* analyzer, float* maxVolume, float* weights){
|
||||
if(!analyzer)
|
||||
return -1;
|
||||
return analyzer->GetNote(maxVolume, weights);
|
||||
}
|
||||
|
||||
PtDyWa* PtDyWa_Create(unsigned step){
|
||||
return new PtDyWa(step);
|
||||
}
|
||||
|
||||
void PtDyWa_Free(PtDyWa* analyzer){
|
||||
if(analyzer)
|
||||
delete analyzer;
|
||||
}
|
||||
|
||||
void PtDyWa_SetVolumeTreshold(PtDyWa* analyzer, float threshold){
|
||||
if(!analyzer)
|
||||
return;
|
||||
analyzer->SetVolumeThreshold(threshold);
|
||||
}
|
||||
|
||||
float PtDyWa_GetVolumeThreshold(PtDyWa* analyzer){
|
||||
if(!analyzer)
|
||||
return 0.f;
|
||||
return analyzer->GetVolumeThreshold();
|
||||
}
|
||||
|
||||
void PtDyWa_InputByte(PtDyWa* analyzer, char* data, int sampleCt){
|
||||
if(sampleCt <= 0 || !analyzer)
|
||||
return;
|
||||
float* dataFloat = short2FloatArray(static_cast<short*>(static_cast<void*>(data)), sampleCt);
|
||||
analyzer->input(dataFloat, dataFloat + sampleCt);
|
||||
freeFloatArray(dataFloat);
|
||||
}
|
||||
|
||||
double PtDyWa_FindNote(PtDyWa* analyzer, float* maxVolume){
|
||||
if(!analyzer)
|
||||
return -1;
|
||||
return analyzer->FindNote(maxVolume);
|
||||
}
|
||||
|
||||
/*
|
||||
namespace Native{
|
||||
namespace PitchTracking{
|
||||
|
||||
//CAnalyzer
|
||||
|
||||
void CAnalyzer::Input(array<float>^ data){
|
||||
if(data->Length == 0)
|
||||
return;
|
||||
pin_ptr<float> dataPtr = &data[0];
|
||||
float* dataPtr2 = dataPtr;
|
||||
m_analyzer->input(dataPtr2, dataPtr2 + data->Length);
|
||||
}
|
||||
|
||||
void CAnalyzer::Input(array<short>^ data){
|
||||
if(data->Length == 0)
|
||||
return;
|
||||
pin_ptr<short> dataPtr = &data[0];
|
||||
float* dataFloat = short2FloatArray(dataPtr, data->Length);
|
||||
m_analyzer->input(dataFloat, dataFloat + data->Length);
|
||||
freeFloatArray(dataFloat);
|
||||
}
|
||||
|
||||
void CAnalyzer::Input(array<Byte>^ data){
|
||||
if(data->Length == 0)
|
||||
return;
|
||||
pin_ptr<Byte> dataPtr = &data[0];
|
||||
float* dataFloat = short2FloatArray(static_cast<short*>(static_cast<void*>(dataPtr)), data->Length / 2);
|
||||
m_analyzer->input(dataFloat, dataFloat + data->Length);
|
||||
freeFloatArray(dataFloat);
|
||||
}
|
||||
|
||||
void CAnalyzer::Process(){
|
||||
m_analyzer->process();
|
||||
}
|
||||
|
||||
double CAnalyzer::GetPeak(){
|
||||
return m_analyzer->getPeak();
|
||||
}
|
||||
|
||||
array<CTone^>^ CAnalyzer::GetTones(){
|
||||
Analyzer::tones_t umTones = m_analyzer->getTones();
|
||||
array<CTone^>^ tones = gcnew array<CTone^>(static_cast<int>(umTones.size()));
|
||||
int i = 0;
|
||||
for(auto it=umTones.begin(); it!=umTones.end(); it++, i++){
|
||||
tones[i]=ConvertToneToManaged(*it);
|
||||
}
|
||||
return tones;
|
||||
}
|
||||
|
||||
CTone^ CAnalyzer::FindTone(){
|
||||
return FindTone(65.0);
|
||||
}
|
||||
|
||||
CTone^ CAnalyzer::FindTone(double minFreq){
|
||||
return FindTone(minFreq, 1000.0);
|
||||
}
|
||||
|
||||
CTone^ CAnalyzer::FindTone(double minFreq, double maxFreq){
|
||||
const Tone* tone = m_analyzer->findTone(minFreq, maxFreq);
|
||||
return (tone == NULL) ? nullptr : ConvertToneToManaged(tone);
|
||||
}
|
||||
|
||||
void CAnalyzer::Output(array<float>^ data, float rate){
|
||||
pin_ptr<float> dataPtr = &data[0];
|
||||
m_analyzer->output(dataPtr, dataPtr + data->Length, rate);
|
||||
}
|
||||
|
||||
String^ CAnalyzer::GetId(){
|
||||
return m_id;
|
||||
}
|
||||
|
||||
|
||||
//CPitchTracker
|
||||
|
||||
void CPitchTracker::Init(double baseToneFrequency, int minHalfTone, int maxHalfTone){
|
||||
PitchTracker::Init(baseToneFrequency, minHalfTone, maxHalfTone);
|
||||
}
|
||||
|
||||
void CPitchTracker::DeInit(){
|
||||
PitchTracker::DeInit();
|
||||
}
|
||||
|
||||
int CPitchTracker::GetTone(array<short>^ samples, array<float>^ weights){
|
||||
if(samples->Length == 0)
|
||||
return -1;
|
||||
pin_ptr<short> dataPtr = &samples[0];
|
||||
pin_ptr<float> weightsPtr = &weights[0];
|
||||
double* dataDouble = short2DoubleArray(dataPtr, samples->Length, false);
|
||||
int result = PitchTracker::GetTone(dataDouble, samples->Length, weightsPtr, true);
|
||||
freeDoubleArray(dataDouble);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include "performous/pitch.hh"
|
||||
#include "ptAKF.h"
|
||||
#include "dywapitchtrack/ptDyWa.h"
|
||||
|
||||
#define DllExport extern "C" __declspec(dllexport)
|
||||
|
||||
DllExport Analyzer* Analyzer_Create(unsigned step);
|
||||
DllExport void Analyzer_Free(Analyzer* analyzer);
|
||||
DllExport void Analyzer_InputFloat(Analyzer* analyzer, float* data, int sampleCt);
|
||||
DllExport void Analyzer_InputShort(Analyzer* analyzer, short* data, int sampleCt);
|
||||
DllExport void Analyzer_InputByte(Analyzer* analyzer, char* data, int sampleCt);
|
||||
DllExport void Analyzer_Process(Analyzer* analyzer);
|
||||
DllExport float Analyzer_GetPeak(Analyzer* analyzer);
|
||||
DllExport double Analyzer_FindNote(Analyzer* analyzer, double minFreq, double maxFreq);
|
||||
DllExport bool Analyzer_OutputFloat(Analyzer* analyzer, float* data, int sampleCt, float rate);
|
||||
|
||||
DllExport PtAKF* PtAKF_Create(unsigned step);
|
||||
DllExport void PtAKF_Free(PtAKF* analyzer);
|
||||
DllExport int PtAKF_GetNumHalfTones();
|
||||
DllExport void PtAKF_SetVolumeThreshold(PtAKF* analyzer, float threshold);
|
||||
DllExport float PtAKF_GetVolumeThreshold(PtAKF* analyzer);
|
||||
DllExport void PtAKF_InputByte(PtAKF* analyzer, char* data, int sampleCt);
|
||||
DllExport int PtAKF_GetNote(PtAKF* analyzer, float* maxVolume, float* weights);
|
||||
|
||||
DllExport PtDyWa* PtDyWa_Create(unsigned step);
|
||||
DllExport void PtDyWa_Free(PtDyWa* analyzer);
|
||||
DllExport void PtDyWa_SetVolumeTreshold(PtDyWa* analyzer,float threshold);
|
||||
DllExport float PtDyWa_GetVolumeThreshold(PtDyWa* analyzer);
|
||||
DllExport void PtDyWa_InputByte(PtDyWa* analyzer, char* data, int sampleCt);
|
||||
DllExport double PtDyWa_FindNote(PtDyWa* analyzer, float* maxVolume);
|
||||
|
||||
/*
|
||||
public ref class CTone {
|
||||
public:
|
||||
static const int MAXHARM = 48; ///< The maximum number of harmonics tracked
|
||||
static const int MINAGE = 2; ///< The minimum age required for a tone to be output
|
||||
double Freq; ///< Frequency (Hz)
|
||||
double DB; ///< Level (dB)
|
||||
double Stabledb; ///< Stable level, useful for graphics rendering
|
||||
int Age; ///< How many times the tone has been detected in row
|
||||
double NoteExact;
|
||||
int Note;
|
||||
/// Less-than compare by levels (instead of frequencies like operator< does)
|
||||
static bool dbCompare(CTone l, CTone r) { return l.DB < r.DB; }
|
||||
};
|
||||
|
||||
public ref class CAnalyzer {
|
||||
public:
|
||||
CAnalyzer(double rate, String^ id) {
|
||||
m_analyzer = new Analyzer(rate,"");
|
||||
m_id = id;
|
||||
}
|
||||
CAnalyzer(double rate, String^ id, unsigned step) {
|
||||
m_analyzer = new Analyzer(rate,"",step);
|
||||
m_id = id;
|
||||
}
|
||||
~CAnalyzer() { this->!CAnalyzer(); }
|
||||
!CAnalyzer() { delete m_analyzer; }
|
||||
|
||||
void Input(array<float>^ data);
|
||||
void Input(array<short>^ data);
|
||||
void Input(array<Byte>^ data);
|
||||
void Process();
|
||||
double GetPeak();
|
||||
array<CTone^>^ GetTones();
|
||||
CTone^ FindTone();
|
||||
CTone^ FindTone(double minFreq);
|
||||
CTone^ FindTone(double minFreq, double maxFreq);
|
||||
void Output(array<float>^ data, float rate);
|
||||
String^ GetId();
|
||||
|
||||
private:
|
||||
Analyzer* m_analyzer;
|
||||
String^ m_id;
|
||||
};
|
||||
|
||||
public ref class CPitchTracker abstract sealed{
|
||||
public:
|
||||
static void Init(double baseToneFrequency, int minHalfTone, int maxHalfTone);
|
||||
static void DeInit();
|
||||
static int GetTone(array<short>^ samples, array<float>^ weights);
|
||||
};
|
||||
*/
|
||||
Binary file not shown.
@@ -0,0 +1,30 @@
|
||||
#include "compatibility.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#if(_MSC_VER < 1800) // < VS2013
|
||||
namespace std
|
||||
{
|
||||
float round(float d)
|
||||
{
|
||||
return static_cast<float>(floor(d + 0.5));
|
||||
}
|
||||
|
||||
double round(double d)
|
||||
{
|
||||
return floor(d + 0.5);
|
||||
}
|
||||
|
||||
long double round(long double d)
|
||||
{
|
||||
return floor(d + 0.5);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
double remainder(double num, double denom){
|
||||
return num - std::round(num/denom) * denom;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
//Contains some definitions to make the code work with compilers that do not support all feautures
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define constexpr const
|
||||
#if(_MSC_VER < 1800) // < VS2013
|
||||
namespace std
|
||||
{
|
||||
float round(float d);
|
||||
double round(double d);
|
||||
long double round(long double d);
|
||||
}
|
||||
|
||||
double remainder(double num, double denom);
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
dywapitchtrack pitch tracking library
|
||||
Documentation
|
||||
|
||||
The dywapitchtrack library computes the pitch of an audio stream in real time. The pitch is the main frequency of the waveform (the 'note' being played or sung). It is expressed as a float in Hz.
|
||||
|
||||
Unlike the human ear, pitch detection is difficult to achieve for computers. Many algorithms have been designed and experimented, but there is no 'best' algorithm. They all depend on the context and the tradeoffs acceptable in terms of speed and latency. The context includes the quality and 'cleanness' of the audio : obviously polyphonic sounds (multiple instruments playing different notes at the same time) are extremely difficult to track, percussive or noisy audio has no pitch, most real-life audio have some noisy moments, some instruments have a lot of harmonics, etc...
|
||||
|
||||
The dywapitchtrack is based on a custom-tailored algorithm which is of very high quality: both very accurate (precision < 0.05 semitones), very low latency (< 23 ms) and very low error rate. It has been thoroughly tested on human voice.
|
||||
|
||||
It can best be described as a dynamic wavelet algorithm (dywa):
|
||||
|
||||
The heart of the algorithm is a very powerful wavelet algorithm, described in a paper by Eric Larson and Ross Maddox :
|
||||
"Real-Time Time-Domain Pitch Tracking Using Wavelets" http://online.physics.uiuc.edu/courses/phys406/NSF_REU_Reports/2005_reu/Real-Time_Time-Domain_Pitch_Tracking_Using_Wavelets.pdf
|
||||
|
||||
This central algorithm has been improved by adding dynamic tracking, to reduce the common problems of frequency-halving and voiced/unvoiced errors. This dynamic tracking explains the need for a tracking structure (dywapitchtracker). The dynamic tracking assumes that the main function dywapitch_computepitch is called repeatedly, as it follows the pitch over time and makes assumptions about human voice capabilities and reallife conditions (as documented inside the code).
|
||||
|
||||
Note : The algorithm currently assumes a 44100Hz audio sampling rate. If you use a different samplerate, you can just multiply the resulting pitch by the ratio between your samplerate and 44100.
|
||||
|
||||
See the dywapitchtrack.h file for the library API documentation.
|
||||
|
||||
History :
|
||||
This algorithm has been designed during a mission for a customer. As I had kept the author's rights on the source code, I eventually decided to make this code public and open source, because it is of really high quality. This algorithms has been extensibely tested, especially it has been included in an Adobe Director Xtra (plugin), asPitchDetect Xtra, that has been distributed widely for a few years.
|
||||
|
||||
I hope that it can be used with pleasure and efficiency in your project.
|
||||
|
||||
Thank you
|
||||
Antoine Schmitt April 2010
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
dywapitchtrack
|
||||
Dynamic Wavelet Algorithm Pitch Tracking library
|
||||
|
||||
Copyright (c) 2010 Antoine Schmitt as@schmittmachine.com
|
||||
|
||||
-- Documentation
|
||||
|
||||
See the dywapitchtrack.h file for the library API documentation.
|
||||
|
||||
|
||||
-- Version history
|
||||
|
||||
Inital release : Antoine Schmitt April 2010
|
||||
jan 2012 : Minor update : changed the link to the Eric Larson and Ross Maddox paper
|
||||
apr 2012 : Included the SpeakHere iPhone app example
|
||||
|
||||
-- Building, Installing
|
||||
|
||||
- the algorithm can be included directly in your project by including the .h and .c files.
|
||||
- the iPhone/dywapitchtrack.xcodeproj builds an iPhone static library, against which one can statically link an iPhone application.
|
||||
|
||||
|
||||
-- MIT open source licence
|
||||
|
||||
Copyright (c) 2010 Antoine Schmitt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,451 @@
|
||||
/* dywapitchtrack.c
|
||||
|
||||
Dynamic Wavelet Algorithm Pitch Tracking library
|
||||
Released under the MIT open source licence
|
||||
|
||||
Copyright (c) 2010 Antoine Schmitt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "dywapitchtrack.h"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h> // for memset
|
||||
|
||||
|
||||
//**********************
|
||||
// Utils
|
||||
//**********************
|
||||
|
||||
#ifndef max
|
||||
#define max(x, y) ((x) > (y)) ? (x) : (y)
|
||||
#endif
|
||||
#ifndef min
|
||||
#define min(x, y) ((x) < (y)) ? (x) : (y)
|
||||
#endif
|
||||
|
||||
// returns 1 if power of 2
|
||||
int _power2p(int value) {
|
||||
if (value == 0) return 1;
|
||||
if (value == 2) return 1;
|
||||
if (value & 0x1) return 0;
|
||||
return (_power2p(value >> 1));
|
||||
}
|
||||
|
||||
// count number of bits
|
||||
int _bitcount(int value) {
|
||||
if (value == 0) return 0;
|
||||
if (value == 1) return 1;
|
||||
if (value == 2) return 2;
|
||||
return _bitcount(value >> 1) + 1;
|
||||
}
|
||||
|
||||
// closest power of 2 above or equal
|
||||
int _ceil_power2(int value) {
|
||||
if (_power2p(value)) return value;
|
||||
|
||||
if (value == 1) return 2;
|
||||
int j, i = _bitcount(value);
|
||||
int res = 1;
|
||||
for (j = 0; j < i; j++) res <<= 1;
|
||||
return res;
|
||||
}
|
||||
|
||||
// closest power of 2 below or equal
|
||||
int _floor_power2(int value) {
|
||||
if (_power2p(value)) return value;
|
||||
return _ceil_power2(value)/2;
|
||||
}
|
||||
|
||||
// abs value
|
||||
int _iabs(int x) {
|
||||
if (x >= 0) return x;
|
||||
return -x;
|
||||
}
|
||||
|
||||
// 2 power
|
||||
int _2power(int i) {
|
||||
int res = 1, j;
|
||||
for (j = 0; j < i; j++) res <<= 1;
|
||||
return res;
|
||||
}
|
||||
|
||||
//******************************
|
||||
// the Wavelet algorithm itself
|
||||
//******************************
|
||||
|
||||
int dywapitch_neededsamplecount(int minFreq) {
|
||||
int nbSam = 3*44100/minFreq; // 1017. for 130 Hz
|
||||
nbSam = _ceil_power2(nbSam); // 1024
|
||||
return nbSam;
|
||||
}
|
||||
|
||||
typedef struct _minmax {
|
||||
int index;
|
||||
struct _minmax *next;
|
||||
} minmax;
|
||||
|
||||
double _dywapitch_computeWaveletPitch(double * samples, int startsample, int samplecount, float* maxVolume, double volThreshold) {
|
||||
double pitchF = 0.0;
|
||||
|
||||
int i, j;
|
||||
double si, si1;
|
||||
|
||||
// must be a power of 2
|
||||
samplecount = _floor_power2(samplecount);
|
||||
|
||||
double *sam = (double *)malloc(sizeof(double)*samplecount);
|
||||
memcpy(sam, samples + startsample, sizeof(double)*samplecount);
|
||||
int curSamNb = samplecount;
|
||||
|
||||
int *distances = (int *)malloc(sizeof(int)*samplecount);
|
||||
int *mins = (int *)malloc(sizeof(int)*samplecount);
|
||||
int *maxs = (int *)malloc(sizeof(int)*samplecount);
|
||||
int nbMins, nbMaxs;
|
||||
|
||||
// algorithm parameters
|
||||
int maxFLWTlevels = 6;
|
||||
double maxF = 3000.;
|
||||
int differenceLevelsN = 3;
|
||||
double maximaThresholdRatio = 0.75;
|
||||
|
||||
double ampltitudeThreshold;
|
||||
double theDC = 0.0;
|
||||
|
||||
{ // compute ampltitudeThreshold and theDC
|
||||
//first compute the DC and maxAMplitude
|
||||
double maxValue = 0.0;
|
||||
double minValue = 0.0;
|
||||
for (i = 0; i < samplecount;i++) {
|
||||
si = sam[i];
|
||||
theDC = theDC + si;
|
||||
if (si > maxValue) maxValue = si;
|
||||
if (si < minValue) minValue = si;
|
||||
}
|
||||
theDC = theDC/samplecount;
|
||||
maxValue = maxValue - theDC;
|
||||
minValue = minValue - theDC;
|
||||
double amplitudeMax = (maxValue > -minValue ? maxValue : -minValue);
|
||||
*maxVolume = static_cast<float>(amplitudeMax + theDC);
|
||||
if(*maxVolume < volThreshold)
|
||||
return 0.0;
|
||||
|
||||
ampltitudeThreshold = amplitudeMax*maximaThresholdRatio;
|
||||
//asLog("dywapitch theDC=%f ampltitudeThreshold=%f\n", theDC, ampltitudeThreshold);
|
||||
|
||||
}
|
||||
|
||||
// levels, start without downsampling..
|
||||
int curLevel = 0;
|
||||
double curModeDistance = -1.;
|
||||
int delta;
|
||||
|
||||
while(1) {
|
||||
|
||||
// delta
|
||||
delta = static_cast<int>(44100./(_2power(curLevel)*maxF));
|
||||
//("dywapitch doing level=%ld delta=%ld\n", curLevel, delta);
|
||||
|
||||
if (curSamNb < 2) goto cleanup;
|
||||
|
||||
// compute the first maximums and minumums after zero-crossing
|
||||
// store if greater than the min threshold
|
||||
// and if at a greater distance than delta
|
||||
double dv, previousDV = -1000;
|
||||
nbMins = nbMaxs = 0;
|
||||
int lastMinIndex = -1000000;
|
||||
int lastmaxIndex = -1000000;
|
||||
int findMax = 0;
|
||||
int findMin = 0;
|
||||
for (i = 2; i < curSamNb; i++) {
|
||||
si = sam[i] - theDC;
|
||||
si1 = sam[i-1] - theDC;
|
||||
|
||||
if (si1 <= 0 && si > 0) findMax = 1;
|
||||
if (si1 >= 0 && si < 0) findMin = 1;
|
||||
|
||||
// min or max ?
|
||||
dv = si - si1;
|
||||
|
||||
if (previousDV > -1000) {
|
||||
|
||||
if (findMin && previousDV < 0 && dv >= 0) {
|
||||
// minimum
|
||||
if (fabs(si) >= ampltitudeThreshold) {
|
||||
if (i > lastMinIndex + delta) {
|
||||
mins[nbMins++] = i;
|
||||
lastMinIndex = i;
|
||||
findMin = 0;
|
||||
//if DEBUGG then put "min ok"&&si
|
||||
//
|
||||
} else {
|
||||
//if DEBUGG then put "min too close to previous"&&(i - lastMinIndex)
|
||||
//
|
||||
}
|
||||
} else {
|
||||
// if DEBUGG then put "min "&abs(si)&" < thresh = "&ltitudeThreshold
|
||||
//--
|
||||
}
|
||||
}
|
||||
|
||||
if (findMax && previousDV > 0 && dv <= 0) {
|
||||
// maximum
|
||||
if (fabs(si) >= ampltitudeThreshold) {
|
||||
if (i > lastmaxIndex + delta) {
|
||||
maxs[nbMaxs++] = i;
|
||||
lastmaxIndex = i;
|
||||
findMax = 0;
|
||||
} else {
|
||||
//if DEBUGG then put "max too close to previous"&&(i - lastmaxIndex)
|
||||
//--
|
||||
}
|
||||
} else {
|
||||
//if DEBUGG then put "max "&abs(si)&" < thresh = "&ltitudeThreshold
|
||||
//--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousDV = dv;
|
||||
}
|
||||
|
||||
if (nbMins == 0 && nbMaxs == 0) {
|
||||
// no best distance !
|
||||
//asLog("dywapitch no mins nor maxs, exiting\n");
|
||||
|
||||
// if DEBUGG then put "no mins nor maxs, exiting"
|
||||
goto cleanup;
|
||||
}
|
||||
//if DEBUGG then put count(maxs)&&"maxs &"&&count(mins)&&"mins"
|
||||
|
||||
// maxs = [5, 20, 100,...]
|
||||
// compute distances
|
||||
int d;
|
||||
memset(distances, 0, samplecount*sizeof(int));
|
||||
for (i = 0 ; i < nbMins ; i++) {
|
||||
for (j = 1; j < differenceLevelsN; j++) {
|
||||
if (i+j < nbMins) {
|
||||
d = _iabs(mins[i] - mins[i+j]);
|
||||
//asLog("dywapitch i=%ld j=%ld d=%ld\n", i, j, d);
|
||||
distances[d] = distances[d] + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i = 0 ; i < nbMaxs ; i++) {
|
||||
for (j = 1; j < differenceLevelsN; j++) {
|
||||
if (i+j < nbMaxs) {
|
||||
d = _iabs(maxs[i] - maxs[i+j]);
|
||||
//asLog("dywapitch i=%ld j=%ld d=%ld\n", i, j, d);
|
||||
distances[d] = distances[d] + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find best summed distance
|
||||
int bestDistance = -1;
|
||||
int bestValue = -1;
|
||||
for (i = 0; i< curSamNb; i++) {
|
||||
int summed = 0;
|
||||
for (j = -delta ; j <= delta ; j++) {
|
||||
if (i+j >=0 && i+j < curSamNb)
|
||||
summed += distances[i+j];
|
||||
}
|
||||
//asLog("dywapitch i=%ld summed=%ld bestDistance=%ld\n", i, summed, bestDistance);
|
||||
if (summed == bestValue) {
|
||||
if (i == 2*bestDistance)
|
||||
bestDistance = i;
|
||||
|
||||
} else if (summed > bestValue) {
|
||||
bestValue = summed;
|
||||
bestDistance = i;
|
||||
}
|
||||
}
|
||||
//asLog("dywapitch bestDistance=%ld\n", bestDistance);
|
||||
|
||||
// averaging
|
||||
double distAvg = 0.0;
|
||||
double nbDists = 0;
|
||||
for (j = -delta ; j <= delta ; j++) {
|
||||
if (bestDistance+j >=0 && bestDistance+j < samplecount) {
|
||||
int nbDist = distances[bestDistance+j];
|
||||
if (nbDist > 0) {
|
||||
nbDists += nbDist;
|
||||
distAvg += (bestDistance+j)*nbDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
// this is our mode distance !
|
||||
distAvg /= nbDists;
|
||||
//asLog("dywapitch distAvg=%f\n", distAvg);
|
||||
|
||||
// continue the levels ?
|
||||
if (curModeDistance > -1.) {
|
||||
double similarity = fabs(distAvg*2 - curModeDistance);
|
||||
if (similarity <= 2*delta) {
|
||||
//if DEBUGG then put "similarity="&similarity&&"delta="&delta&&"ok"
|
||||
//asLog("dywapitch similarity=%f OK !\n", similarity);
|
||||
// two consecutive similar mode distances : ok !
|
||||
pitchF = 44100./(_2power(curLevel-1)*curModeDistance);
|
||||
goto cleanup;
|
||||
}
|
||||
//if DEBUGG then put "similarity="&similarity&&"delta="&delta&&"not"
|
||||
}
|
||||
|
||||
// not similar, continue next level
|
||||
curModeDistance = distAvg;
|
||||
|
||||
curLevel = curLevel + 1;
|
||||
if (curLevel >= maxFLWTlevels) {
|
||||
// put "max levels reached, exiting"
|
||||
//asLog("dywapitch max levels reached, exiting\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// downsample
|
||||
if (curSamNb < 2) {
|
||||
//asLog("dywapitch not enough samples, exiting\n");
|
||||
goto cleanup;
|
||||
}
|
||||
for (i = 0; i < curSamNb/2; i++) {
|
||||
sam[i] = (sam[2*i] + sam[2*i + 1])/2.;
|
||||
}
|
||||
curSamNb /= 2;
|
||||
}
|
||||
|
||||
///
|
||||
cleanup:
|
||||
free(distances);
|
||||
free(mins);
|
||||
free(maxs);
|
||||
free(sam);
|
||||
|
||||
return pitchF;
|
||||
}
|
||||
|
||||
// ***********************************
|
||||
// the dynamic postprocess
|
||||
// ***********************************
|
||||
|
||||
/***
|
||||
It states:
|
||||
- a pitch cannot change much all of a sudden (20%) (impossible humanly,
|
||||
so if such a situation happens, consider that it is a mistake and drop it.
|
||||
- a pitch cannot double or be divided by 2 all of a sudden : it is an
|
||||
algorithm side-effect : divide it or double it by 2.
|
||||
- a lonely voiced pitch cannot happen, nor can a sudden drop in the middle
|
||||
of a voiced segment. Smooth the plot.
|
||||
***/
|
||||
|
||||
double _dywapitch_dynamicprocess(dywapitchtracker *pitchtracker, double pitch) {
|
||||
|
||||
// equivalence
|
||||
if (pitch == 0.0) pitch = -1.0;
|
||||
|
||||
//
|
||||
double estimatedPitch = -1;
|
||||
double acceptedError = 0.2f;
|
||||
int maxConfidence = 5;
|
||||
|
||||
if (pitch != -1) {
|
||||
// I have a pitch here
|
||||
|
||||
if (pitchtracker->_prevPitch == -1) {
|
||||
// no previous
|
||||
estimatedPitch = pitch;
|
||||
pitchtracker->_prevPitch = pitch;
|
||||
pitchtracker->_pitchConfidence = 1;
|
||||
|
||||
} else if (abs(pitchtracker->_prevPitch - pitch)/pitch < acceptedError) {
|
||||
// similar : remember and increment pitch
|
||||
pitchtracker->_prevPitch = pitch;
|
||||
estimatedPitch = pitch;
|
||||
pitchtracker->_pitchConfidence = min(maxConfidence, pitchtracker->_pitchConfidence + 1); // maximum 3
|
||||
|
||||
} else if ((pitchtracker->_pitchConfidence >= maxConfidence-2) && abs(pitchtracker->_prevPitch - 2.*pitch)/(2.*pitch) < acceptedError) {
|
||||
// close to half the last pitch, which is trusted
|
||||
estimatedPitch = 2.*pitch;
|
||||
pitchtracker->_prevPitch = estimatedPitch;
|
||||
|
||||
} else if ((pitchtracker->_pitchConfidence >= maxConfidence-2) && abs(pitchtracker->_prevPitch - 0.5*pitch)/(0.5*pitch) < acceptedError) {
|
||||
// close to twice the last pitch, which is trusted
|
||||
estimatedPitch = 0.5*pitch;
|
||||
pitchtracker->_prevPitch = estimatedPitch;
|
||||
|
||||
} else {
|
||||
// nothing like this : very different value
|
||||
if (pitchtracker->_pitchConfidence >= 1) {
|
||||
// previous trusted : keep previous
|
||||
estimatedPitch = pitchtracker->_prevPitch;
|
||||
pitchtracker->_pitchConfidence = max(0, pitchtracker->_pitchConfidence - 1);
|
||||
} else {
|
||||
// previous not trusted : take current
|
||||
estimatedPitch = pitch;
|
||||
pitchtracker->_prevPitch = pitch;
|
||||
pitchtracker->_pitchConfidence = 1;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// no pitch now
|
||||
if (pitchtracker->_prevPitch != -1) {
|
||||
// was pitch before
|
||||
if (pitchtracker->_pitchConfidence >= 1) {
|
||||
// continue previous
|
||||
estimatedPitch = pitchtracker->_prevPitch;
|
||||
pitchtracker->_pitchConfidence = max(0, pitchtracker->_pitchConfidence - 1);
|
||||
} else {
|
||||
pitchtracker->_prevPitch = -1;
|
||||
estimatedPitch = -1.;
|
||||
pitchtracker->_pitchConfidence = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// put "_pitchConfidence="&pitchtracker->_pitchConfidence
|
||||
if (pitchtracker->_pitchConfidence >= 1) {
|
||||
// ok
|
||||
pitch = estimatedPitch;
|
||||
} else {
|
||||
pitch = -1;
|
||||
}
|
||||
|
||||
// equivalence
|
||||
if (pitch == -1) pitch = 0.0;
|
||||
|
||||
return pitch;
|
||||
}
|
||||
|
||||
|
||||
// ************************************
|
||||
// the API main entry points
|
||||
// ************************************
|
||||
|
||||
void dywapitch_inittracking(dywapitchtracker *pitchtracker) {
|
||||
pitchtracker->_prevPitch = -1.;
|
||||
pitchtracker->_pitchConfidence = -1;
|
||||
}
|
||||
|
||||
double dywapitch_computepitch(dywapitchtracker *pitchtracker, double * samples, int startsample, int samplecount, float* maxVolume, double volThreshold) {
|
||||
double raw_pitch = _dywapitch_computeWaveletPitch(samples, startsample, samplecount, maxVolume, volThreshold);
|
||||
return _dywapitch_dynamicprocess(pitchtracker, raw_pitch);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/* dywapitchtrack.h
|
||||
|
||||
Dynamic Wavelet Algorithm Pitch Tracking library
|
||||
Released under the MIT open source licence
|
||||
|
||||
Copyright (c) 2010 Antoine Schmitt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* Documentation
|
||||
|
||||
The dywapitchtrack library computes the pitch of an audio stream in real time.
|
||||
|
||||
The pitch is the main frequency of the waveform (the 'note' being played or sung).
|
||||
It is expressed as a float in Hz.
|
||||
|
||||
Unlike the human ear, pitch detection is difficult to achieve for computers. Many
|
||||
algorithms have been designed and experimented, but there is no 'best' algorithm.
|
||||
They all depend on the context and the tradeoffs acceptable in terms of speed and
|
||||
latency. The context includes the quality and 'cleanness' of the audio : obviously
|
||||
polyphonic sounds (multiple instruments playing different notes at the same time)
|
||||
are extremely difficult to track, percussive or noisy audio has no pitch, most
|
||||
real-life audio have some noisy moments, some instruments have a lot of harmonics,
|
||||
etc...
|
||||
|
||||
The dywapitchtrack is based on a custom-tailored algorithm which is of very high quality:
|
||||
both very accurate (precision < 0.05 semitones), very low latency (< 23 ms) and
|
||||
very low error rate. It has been thoroughly tested on human voice.
|
||||
|
||||
It can best be described as a dynamic wavelet algorithm (dywa):
|
||||
|
||||
The heart of the algorithm is a very powerful wavelet algorithm, described in a paper
|
||||
by Eric Larson and Ross Maddox : "Real-Time Time-Domain Pitch Tracking Using Wavelets"
|
||||
http://online.physics.uiuc.edu/courses/phys498pom/NSF_REU_Reports/2005_reu/Real-Time_Time-Domain_Pitch_Tracking_Using_Wavelets.pdf
|
||||
|
||||
This central algorithm has been improved by adding dynamic tracking, to reduce the
|
||||
common problems of frequency-halving and voiced/unvoiced errors. This dynamic tracking
|
||||
explains the need for a tracking structure (dywapitchtracker). The dynamic tracking assumes
|
||||
that the main function dywapitch_computepitch is called repeatedly, as it follows the pitch
|
||||
over time and makes assumptions about human voice capabilities and reallife conditions
|
||||
(as documented inside the code).
|
||||
|
||||
Note : The algorithm currently assumes a 44100Hz audio sampling rate. If you use a different
|
||||
samplerate, you can just multiply the resulting pitch by the ratio between your samplerate and 44100.
|
||||
*/
|
||||
|
||||
/* Usage
|
||||
|
||||
// Allocate your audio buffers and start the audio stream.
|
||||
// Allocate a 'dywapitchtracker' structure.
|
||||
// Start the pitch tracking by calling 'dywapitch_inittracking'.
|
||||
dywapitchtracker pitchtracker;
|
||||
dywapitch_inittracking(&pitchtracker);
|
||||
|
||||
// For each available audio buffer, call 'dywapitch_computepitch'
|
||||
double thepitch = dywapitch_computepitch(&pitchtracker, samples, start, count);
|
||||
|
||||
*/
|
||||
|
||||
#ifndef dywapitchtrack__H
|
||||
#define dywapitchtrack__H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// structure to hold tracking data
|
||||
typedef struct _dywapitchtracker {
|
||||
double _prevPitch;
|
||||
int _pitchConfidence;
|
||||
} dywapitchtracker;
|
||||
|
||||
// returns the number of samples needed to compute pitch for fequencies equal and above the given minFreq (in Hz)
|
||||
// useful to allocate large enough audio buffer
|
||||
// ex : for frequencies above 130Hz, you need 1024 samples (assuming a 44100 Hz samplerate)
|
||||
int dywapitch_neededsamplecount(int minFreq);
|
||||
|
||||
// call before computing any pitch, passing an allocated dywapitchtracker structure
|
||||
void dywapitch_inittracking(dywapitchtracker *pitchtracker);
|
||||
|
||||
// computes the pitch. Pass the inited dywapitchtracker structure
|
||||
// samples : a pointer to the sample buffer
|
||||
// startsample : the index of the first sample to use in the sample buffer
|
||||
// samplecount : the number of samples to use to compte the pitch
|
||||
// return 0.0 if no pitch was found (sound too low, noise, etc..)
|
||||
double dywapitch_computepitch(dywapitchtracker *pitchtracker, double * samples, int startsample, int samplecount, float* maxVolume, double volThreshold);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "ptDyWa.h"
|
||||
#include "../Helper.h"
|
||||
|
||||
|
||||
PtDyWa::PtDyWa(unsigned step){
|
||||
_Step = step;
|
||||
dywapitch_inittracking(&_State);
|
||||
_VolTreshold = 0.01f;
|
||||
_LastMaxVol = 0.f;
|
||||
}
|
||||
|
||||
void PtDyWa::SetVolumeThreshold(float threshold){
|
||||
_VolTreshold = threshold;
|
||||
}
|
||||
|
||||
double PtDyWa::FindNote(float* maxVolume){
|
||||
double AnaylsisBuf[_SampleCt];
|
||||
size_t size = _AnalysisBuf.size();
|
||||
if(size > _SampleCt)
|
||||
_AnalysisBuf.pop(size - _SampleCt);
|
||||
if(!_AnalysisBuf.read(AnaylsisBuf, AnaylsisBuf + _SampleCt)){
|
||||
*maxVolume = _LastMaxVol * 0.85f;
|
||||
return -1;
|
||||
}
|
||||
_AnalysisBuf.pop(_Step);
|
||||
double pitch = dywapitch_computepitch(&_State, AnaylsisBuf, 0, _SampleCt, maxVolume, _VolTreshold);
|
||||
if(pitch == 0.0)
|
||||
return -1;
|
||||
else
|
||||
return FreqToNote(pitch);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include "../performous/pitch.hh"
|
||||
#include "dywapitchtrack.h"
|
||||
|
||||
class PtDyWa{
|
||||
public:
|
||||
PtDyWa(unsigned step);
|
||||
|
||||
/** Add input data to buffer. This is thread-safe (against other functions). **/
|
||||
template <typename InIt> void input(InIt begin, InIt end) {
|
||||
_AnalysisBuf.insert(begin, end);
|
||||
}
|
||||
|
||||
double FindNote(float* maxVolume);
|
||||
void SetVolumeThreshold(float threshold);
|
||||
float GetVolumeThreshold(){return _VolTreshold;}
|
||||
private:
|
||||
dywapitchtracker _State;
|
||||
constexpr static size_t _SampleCt = 2048;
|
||||
RingBuffer<_SampleCt * 2> _AnalysisBuf;
|
||||
unsigned _Step;
|
||||
float _VolTreshold;
|
||||
float _LastMaxVol;
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
code is based on Performous: https://github.com/performous/performous/tree/master/game
|
||||
Some modifications were made to make it compile with VS2012, fix warnings and make it suitable for our needs
|
||||
From 25.03.2014
|
||||
@@ -0,0 +1,346 @@
|
||||
GNU GPLv2 or later. Some parts of the project may have more liberal licenses.
|
||||
See Authors.txt for information on copyright holders and licenses of specific
|
||||
parts. Fonts are licensed under SIL OFL.
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file fft.hpp FFT and related facilities.
|
||||
*/
|
||||
|
||||
#include "sample.hpp"
|
||||
#include <complex>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.141592653589793
|
||||
#endif
|
||||
|
||||
#define M_TAU (2.0 * M_PI)
|
||||
|
||||
namespace da {
|
||||
|
||||
// With g++ optimization -fcx-limited-range should be used for 5x performance boost.
|
||||
|
||||
// Based on the description of Volodymyr Myrnyy in
|
||||
// http://www.dspdesignline.com/showArticle.jhtml?printableArticle=true&articleId=199903272
|
||||
template<unsigned P, typename T> struct DanielsonLanczos {
|
||||
static void apply(std::complex<T>* data) {
|
||||
constexpr std::size_t N = 1 << P;
|
||||
constexpr std::size_t M = N / 2;
|
||||
// Compute even and odd halves
|
||||
DanielsonLanczos<P - 1, T>().apply(data);
|
||||
DanielsonLanczos<P - 1, T>().apply(data + M);
|
||||
// Combine the results
|
||||
std::complex<T> w(1.0);
|
||||
for (std::size_t i = 0; i < M; ++i) {
|
||||
const std::complex<T> temp = data[i + M] * w;
|
||||
data[M + i] = data[i] - temp;
|
||||
data[i] += temp;
|
||||
w *= std::polar(1.0, -M_TAU / N);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> struct DanielsonLanczos<0, T> { static void apply(std::complex<T>*) {} };
|
||||
|
||||
/** Perform FFT on data. **/
|
||||
template<unsigned P, typename T> void fft(std::complex<T>* data) {
|
||||
// Perform bit-reversal sorting of sample data.
|
||||
constexpr std::size_t N = 1 << P;
|
||||
std::size_t j = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
if (i < j) std::swap(data[i], data[j]);
|
||||
std::size_t m = N / 2;
|
||||
while (m > 1 && m <= j) { j -= m; m >>= 1; }
|
||||
j += m;
|
||||
}
|
||||
// Do the actual calculation
|
||||
DanielsonLanczos<P, T>::apply(data);
|
||||
}
|
||||
|
||||
/** Perform FFT on data from floating point iterator, windowing the input. **/
|
||||
template<unsigned P, typename InIt, typename Window> std::vector<std::complex<float> > fft(InIt begin, Window window) {
|
||||
std::vector<std::complex<float> > data(1 << P);
|
||||
// Perform bit-reversal sorting of sample data.
|
||||
constexpr std::size_t N = 1 << P;
|
||||
std::size_t j = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
data[j] = *begin++ * window[i];
|
||||
std::size_t m = N / 2;
|
||||
while (m > 1 && m <= j) { j -= m; m >>= 1; }
|
||||
j += m;
|
||||
}
|
||||
// Do the actual calculation
|
||||
DanielsonLanczos<P, float>::apply(&data[0]);
|
||||
return data;
|
||||
}
|
||||
|
||||
template<unsigned P, typename T> void ifft(std::complex<T>* data) {
|
||||
constexpr std::size_t N = 1 << P;
|
||||
for (std::size_t i = 0; i < N; ++i) data[i] = std::conj(data[i]); // Invert phase so that we can use FFT to do IFFT
|
||||
fft<P>(data);
|
||||
constexpr std::complex<T> scale(1.0/N, 0.0);
|
||||
for (std::size_t i = 0; i < N; ++i) data[i] = scale * std::conj(data[i]); // Invert back, and apply IFFT scaling
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
/**
|
||||
* @file sample.hpp Sample format definition and format conversions.
|
||||
*/
|
||||
|
||||
namespace da {
|
||||
|
||||
using std::round;
|
||||
|
||||
// Should be a floating-point type
|
||||
typedef float sample_t;
|
||||
|
||||
// A helper function for clamping a value to a certain range
|
||||
template <typename T> T clamp(T val, T min, T max) {
|
||||
if (val < min) val = min;
|
||||
if (val > max) val = max;
|
||||
return val;
|
||||
}
|
||||
|
||||
constexpr sample_t max_s16(32767), min_s16 = -max_s16 - sample_t(1);
|
||||
constexpr sample_t max_s24(8388607), min_s24 = -max_s24 - sample_t(1);
|
||||
constexpr sample_t max_s32(2147483647), min_s32= -max_s32 - sample_t(1);
|
||||
constexpr double tau(6.2831853071795864769252867665590057683943387987502116); ///< One full circle
|
||||
constexpr double pi = tau / sample_t(2); ///< Half circle
|
||||
constexpr double eps(1e-10); ///< Tiny positive value
|
||||
|
||||
/// Mathematical sinc function
|
||||
template<typename Float> static inline Float msinc(Float x) { return std::abs(x) < Float(eps) ? Float(1) : std::sin(x) / x; }
|
||||
|
||||
/// Normalized (signal processing) sinc function
|
||||
template<typename Float> static inline Float sinc(Float x) { return msinc(Float(pi) * x); }
|
||||
|
||||
/// Lanczos kernel of size A
|
||||
template<unsigned A, typename Float> static inline Float lanc(Float x) {
|
||||
return std::abs(x) < A ? sinc(x) * sinc(x / A) : Float();
|
||||
}
|
||||
|
||||
// The following conversions provide lossless conversions between floats
|
||||
// and integers. Be sure to use only these conversions or otherwise the
|
||||
// conversions may not be lossless, due to different scaling factors being
|
||||
// used by different libraries.
|
||||
|
||||
// The negative minimum integer value produces sample_t value slightly
|
||||
// more negative than -1.0 but this is necessary in order to prevent
|
||||
// clipping in the float-to-int conversions. Now amplitude 1.0 in floating
|
||||
// point produces -32767 .. 32767 symmetrical non-clipping range in s16.
|
||||
|
||||
static inline sample_t conv_from_s16(int s) { return s / max_s16; }
|
||||
static inline sample_t conv_from_s24(int s) { return s / max_s24; }
|
||||
static inline sample_t conv_from_s32(int s) { return s / max_s32; }
|
||||
// The rounding is strictly not necessary, but it greatly improves
|
||||
// the error tolerance if any floating point calculations are done.
|
||||
// The ugly static_casts are required to avoid warnings in MSVC.
|
||||
static inline int conv_to_s16(sample_t s) { return clamp(static_cast<int>(round(s * max_s16)), static_cast<int>(min_s16), static_cast<int>(max_s16)); }
|
||||
static inline int conv_to_s24(sample_t s) { return clamp(static_cast<int>(round(s * max_s24)), static_cast<int>(min_s24), static_cast<int>(max_s24)); }
|
||||
static inline int conv_to_s32(sample_t s) { return static_cast<int>(clamp(round(s * max_s32), min_s32, max_s32 )); }
|
||||
// Non-rounding non-clamping versions are provided for very low end devices (still lossless)
|
||||
static inline int conv_to_s16_fast(sample_t s) { return static_cast<int>(s * max_s16); }
|
||||
static inline int conv_to_s24_fast(sample_t s) { return static_cast<int>(s * max_s24); }
|
||||
static inline int conv_to_s32_fast(sample_t s) { return static_cast<int>(s * max_s32); }
|
||||
|
||||
template <typename ValueType> class step_iterator: public std::iterator<std::random_access_iterator_tag, ValueType> {
|
||||
ValueType* m_pos;
|
||||
std::ptrdiff_t m_step;
|
||||
public:
|
||||
step_iterator(ValueType* pos, std::ptrdiff_t step): m_pos(pos), m_step(step) {}
|
||||
ValueType& operator*() { return *m_pos; }
|
||||
step_iterator operator+(std::ptrdiff_t rhs) { return step_iterator(m_pos + m_step * rhs, m_step); }
|
||||
step_iterator& operator++() { m_pos += m_step; return *this; }
|
||||
step_iterator operator++(int) { step_iterator ret = *this; ++*this; return ret; }
|
||||
bool operator!=(step_iterator const& rhs) const { return m_pos != rhs.m_pos; }
|
||||
std::ptrdiff_t operator-(step_iterator const& rhs) const { return (m_pos - rhs.m_pos) / m_step; }
|
||||
// TODO: more operators
|
||||
};
|
||||
|
||||
typedef step_iterator<sample_t> sample_iterator;
|
||||
typedef step_iterator<sample_t const> sample_const_iterator;
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
#include "pitch.hh"
|
||||
|
||||
#include "util.hh"
|
||||
#include "libda/fft.hpp"
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
// Limit the range to avoid noise and useless computation
|
||||
static const double FFT_MINFREQ = 45.0;
|
||||
static const double FFT_MAXFREQ = 5000.0;
|
||||
|
||||
Tone::Tone():
|
||||
freq(0.0),
|
||||
db(-getInf()),
|
||||
stabledb(-getInf()),
|
||||
age()
|
||||
{
|
||||
for (auto& h: harmonics) h = -getInf();
|
||||
}
|
||||
|
||||
void Tone::print() const {
|
||||
if (age < Tone::MINAGE) return;
|
||||
std::cout << std::fixed << std::setprecision(1) << freq << " Hz, age " << age << ", " << db << " dB:";
|
||||
for (std::size_t i = 0; i < 8; ++i) std::cout << " " << harmonics[i];
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
bool Tone::operator==(double f) const {
|
||||
return std::abs(freq / f - 1.0) < 0.05;
|
||||
}
|
||||
|
||||
Analyzer::Analyzer(double rate, std::string id, unsigned step):
|
||||
m_step(step),
|
||||
m_resampleFactor(1.0),
|
||||
m_resamplePos(),
|
||||
m_rate(rate),
|
||||
m_id(id),
|
||||
m_window(FFT_N),
|
||||
m_fftLastPhase(FFT_N / 2),
|
||||
m_peak(0.0),
|
||||
m_oldfreq(0.0)
|
||||
{
|
||||
if (m_step > FFT_N) throw std::logic_error("Analyzer step is larger that FFT_N (ideally it should be less than a fourth of FFT_N).");
|
||||
// Hamming window
|
||||
for (size_t i=0; i < FFT_N; i++) {
|
||||
m_window[i] = static_cast<float>(0.53836 - 0.46164 * std::cos(2.0 * M_PI * i / (FFT_N - 1)));
|
||||
}
|
||||
}
|
||||
|
||||
bool Analyzer::output(float* begin, float* end, double rate) {
|
||||
constexpr unsigned a = 2;
|
||||
const size_t size = m_passthrough.size();
|
||||
const unsigned out = static_cast<unsigned>((end - begin) / 2 /* stereo */);
|
||||
if (out == 0) return false;
|
||||
const unsigned in = static_cast<unsigned>(m_resampleFactor * (m_rate / rate) * out + 2 * a /* lanczos kernel */ + 5 /* safety margin for rounding errors */);
|
||||
float pcm[m_passthrough.capacity];
|
||||
if(!m_passthrough.read(pcm, pcm + in + 4))
|
||||
return false;
|
||||
for (unsigned i = 0; i < out; ++i) {
|
||||
double s = 0.0;
|
||||
unsigned k = static_cast<unsigned>(m_resamplePos);
|
||||
double x = m_resamplePos - k;
|
||||
// Lanczos sampling of input at m_resamplePos
|
||||
for (unsigned j = 0; j <= 2 * a; ++j) s += pcm[k + j] * da::lanc<a>(x - j + a);
|
||||
s *= 5.0;
|
||||
begin[i * 2] += (float) s;
|
||||
begin[i * 2 + 1] += (float) s;
|
||||
m_resamplePos += m_resampleFactor;
|
||||
}
|
||||
unsigned num = static_cast<unsigned>(m_resamplePos);
|
||||
m_resamplePos -= num;
|
||||
if (size > 3000) {
|
||||
// Reset
|
||||
m_passthrough.pop(size - 700);
|
||||
m_resampleFactor = 1.0;
|
||||
} else {
|
||||
m_passthrough.pop(num);
|
||||
m_resampleFactor = 0.99 * m_resampleFactor + 0.01 * (size > 700 ? 1.02 : 0.98);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
struct Peak {
|
||||
double freq;
|
||||
double db;
|
||||
bool harm[Tone::MAXHARM];
|
||||
Peak(double _freq = 0.0, double _db = -getInf()):
|
||||
freq(_freq), db(_db)
|
||||
{
|
||||
for (auto& h: harm) h = false;
|
||||
}
|
||||
void clear() {
|
||||
freq = 0.0;
|
||||
db = -getInf();
|
||||
}
|
||||
};
|
||||
|
||||
Peak& match(std::vector<Peak>& peaks, std::size_t pos) {
|
||||
std::size_t best = pos;
|
||||
if (peaks[pos - 1].db > peaks[best].db) best = pos - 1;
|
||||
if (peaks[pos + 1].db > peaks[best].db) best = pos + 1;
|
||||
return peaks[best];
|
||||
}
|
||||
}
|
||||
|
||||
bool Analyzer::calcFFT() {
|
||||
float pcm[FFT_N];
|
||||
// Read FFT_N samples, move forward by m_step samples
|
||||
if (!m_buf.read(pcm, pcm + FFT_N)) return false;
|
||||
m_buf.pop(m_step);
|
||||
// Peak level calculation of the most recent m_step samples (the rest is overlap)
|
||||
for (float const* ptr = pcm + FFT_N - m_step; ptr != pcm + FFT_N; ++ptr) {
|
||||
float s = *ptr;
|
||||
float p = s * s;
|
||||
if (p > m_peak) m_peak = p; else m_peak *= 0.999;
|
||||
}
|
||||
// Calculate FFT
|
||||
m_fft = da::fft<FFT_P>(pcm, m_window);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Analyzer::calcTones() {
|
||||
// Precalculated constants
|
||||
const double freqPerBin = m_rate / FFT_N;
|
||||
const double phaseStep = 2.0 * M_PI * m_step / FFT_N;
|
||||
const double normCoeff = 1.0 / FFT_N;
|
||||
const double minMagnitude = pow(10, -100.0 / 20.0) / normCoeff; // -100 dB
|
||||
// Limit frequency range of processing
|
||||
const size_t kMin = std::max(size_t(1), size_t(FFT_MINFREQ / freqPerBin));
|
||||
const size_t kMax = std::min(FFT_N / 2, size_t(FFT_MAXFREQ / freqPerBin));
|
||||
std::vector<Peak> peaks(kMax + 1); // One extra to simplify loops
|
||||
for (size_t k = 1; k <= kMax; ++k) {
|
||||
double magnitude = std::abs(m_fft[k]);
|
||||
double phase = std::arg(m_fft[k]);
|
||||
// process phase difference
|
||||
double delta = phase - m_fftLastPhase[k];
|
||||
m_fftLastPhase[k] = static_cast<float>(phase);
|
||||
delta -= k * phaseStep; // subtract expected phase difference
|
||||
delta = remainder(delta, M_PI); // map delta phase into +/- M_PI interval
|
||||
delta /= phaseStep; // calculate diff from bin center frequency
|
||||
double freq = (k + delta) * freqPerBin; // calculate the true frequency
|
||||
if (freq > 1.0 && magnitude > minMagnitude) {
|
||||
peaks[k].freq = freq;
|
||||
peaks[k].db = 20.0 * log10(normCoeff * magnitude);
|
||||
}
|
||||
}
|
||||
// Prefilter peaks
|
||||
double prevdb = peaks[0].db;
|
||||
for (size_t k = 1; k < kMax; ++k) {
|
||||
double db = peaks[k].db;
|
||||
if (db > prevdb) peaks[k - 1].clear();
|
||||
if (db < prevdb) peaks[k].clear();
|
||||
prevdb = db;
|
||||
}
|
||||
// Find the tones (collections of harmonics) from the array of peaks
|
||||
tones_t tones;
|
||||
for (size_t k = kMax - 1; k >= kMin; --k) {
|
||||
if (peaks[k].db < -70.0) continue;
|
||||
// Find the best divider for getting the fundamental from peaks[k]
|
||||
std::size_t bestDiv = 1;
|
||||
int bestScore = 0;
|
||||
for (std::size_t div = 2; div <= Tone::MAXHARM && k / div > 1; ++div) {
|
||||
double freq = peaks[k].freq / div; // Fundamental
|
||||
double db = peaks[k].db;
|
||||
int score = 0;
|
||||
for (std::size_t n = 1; n < div && n < 8; ++n) {
|
||||
Peak& p = match(peaks, k * n / div);
|
||||
--score;
|
||||
if (std::abs(p.db/db - 1.0) > 0.4 || std::abs(p.freq / n / freq - 1.0) > .04) continue;
|
||||
if (n == 1) score += 1; // Extra for fundamental
|
||||
score += 2;
|
||||
}
|
||||
if (score >= bestScore) {
|
||||
bestScore = score;
|
||||
bestDiv = div;
|
||||
}
|
||||
}
|
||||
// Construct a Tone by combining the fundamental frequency (freq) and all harmonics
|
||||
Tone t;
|
||||
std::size_t count = 0;
|
||||
double freq = peaks[k].freq / bestDiv;
|
||||
t.db = peaks[k].db;
|
||||
for (std::size_t n = 1; n <= bestDiv; ++n) {
|
||||
// Find the peak for n'th harmonic
|
||||
Peak& p = match(peaks, k * n / bestDiv);
|
||||
if (std::abs(p.freq / n / freq - 1.0) > .04) continue; // Does it match the fundamental freq?
|
||||
if (std::abs(p.db/t.db - 1.0) > 0.4) continue;
|
||||
t.db = std::max(t.db, p.db);
|
||||
++count;
|
||||
t.freq += p.freq / n;
|
||||
t.harmonics[n - 1] = p.db;
|
||||
p.clear();
|
||||
}
|
||||
t.freq /= count;
|
||||
// If the tone seems strong enough, add it (-3 dB compensation for each harmonic)
|
||||
if (t.db > -50.0 - 3.0 * count) {
|
||||
t.stabledb = t.db;
|
||||
tones.push_back(t);
|
||||
}
|
||||
}
|
||||
mergeWithOld(tones);
|
||||
m_tones.swap(tones);
|
||||
}
|
||||
|
||||
void Analyzer::mergeWithOld(tones_t& tones) const {
|
||||
tones.sort();
|
||||
auto it = tones.begin();
|
||||
// Iterate over old tones
|
||||
for (auto const& old: m_tones) {
|
||||
// Try to find a matching new tone
|
||||
while (it != tones.end() && *it < old) ++it;
|
||||
// If match found
|
||||
if (it != tones.end() && *it == old) {
|
||||
// Merge the old tone into the new tone
|
||||
it->age = old.age + 1;
|
||||
it->stabledb = 0.8 * old.stabledb + 0.2 * it->db;
|
||||
it->freq = 0.5 * old.freq + 0.5 * it->freq;
|
||||
} else if (old.db > -80.0) {
|
||||
// Insert a decayed version of the old tone into new tones
|
||||
Tone& t = *tones.insert(it, old);
|
||||
t.db -= 5.0;
|
||||
t.stabledb -= 0.1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Analyzer::process() {
|
||||
// Try calculating FFT and calculate tones until no more data in input buffer
|
||||
while (calcFFT()) calcTones();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include "../compatibility.h"
|
||||
|
||||
/// struct to represent tones
|
||||
struct Tone {
|
||||
static const std::size_t MAXHARM = 48; ///< The maximum number of harmonics tracked
|
||||
static const std::size_t MINAGE = 2; ///< The minimum age required for a tone to be output
|
||||
double freq; ///< Frequency (Hz)
|
||||
double db; ///< Level (dB)
|
||||
double stabledb; ///< Stable level, useful for graphics rendering
|
||||
double harmonics[MAXHARM]; ///< Harmonics' levels
|
||||
std::size_t age; ///< How many times the tone has been detected in row
|
||||
Tone();
|
||||
void print() const; ///< Prints Tone to std::cout
|
||||
bool operator==(double f) const; ///< Compare for rough frequency match
|
||||
/// Less-than compare by levels (instead of frequencies like operator< does)
|
||||
static bool dbCompare(Tone const& l, Tone const& r) { return l.db < r.db; }
|
||||
};
|
||||
|
||||
static inline bool operator==(Tone const& lhs, Tone const& rhs) { return lhs == rhs.freq; }
|
||||
static inline bool operator!=(Tone const& lhs, Tone const& rhs) { return !(lhs == rhs); }
|
||||
static inline bool operator<=(Tone const& lhs, Tone const& rhs) { return lhs.freq < rhs.freq || lhs == rhs; }
|
||||
static inline bool operator>=(Tone const& lhs, Tone const& rhs) { return lhs.freq > rhs.freq || lhs == rhs; }
|
||||
static inline bool operator<(Tone const& lhs, Tone const& rhs) { return lhs.freq < rhs.freq && lhs != rhs; }
|
||||
static inline bool operator>(Tone const& lhs, Tone const& rhs) { return lhs.freq > rhs.freq && lhs != rhs; }
|
||||
|
||||
static const unsigned FFT_P = 10;
|
||||
static const std::size_t FFT_N = 1 << FFT_P;
|
||||
|
||||
/// Lock-free ring buffer. Discards oldest data on overflow (not strictly thread-safe).
|
||||
template <size_t SIZE> class RingBuffer {
|
||||
public:
|
||||
constexpr static size_t capacity = SIZE;
|
||||
RingBuffer(): m_read(), m_write() {} ///< Initialize empty buffer
|
||||
template <typename InIt> void insert(InIt begin, InIt end) {
|
||||
size_t r = m_read; // The read position
|
||||
size_t w = m_write; // The write position
|
||||
bool overflow = (static_cast<size_t>(end - begin) >= SIZE - size());
|
||||
while (begin != end) {
|
||||
m_buf[w] = *begin++; // Copy sample
|
||||
w = modulo(w + 1); // Update cursor
|
||||
}
|
||||
m_write = w;
|
||||
if (overflow) m_read = modulo(w + 1); // Reset read pointer on overflow
|
||||
}
|
||||
/// Read data from current position if there is enough data to fill the range (otherwise return false). Does not move read pointer.
|
||||
template <typename OutIt> bool read(OutIt begin, OutIt end) {
|
||||
size_t r = m_read;
|
||||
if (size() < static_cast<size_t>(end - begin)) return false; // Not enough audio available
|
||||
while (begin != end) *begin++ = m_buf[r++ % SIZE]; // Copy audio to output iterator
|
||||
return true;
|
||||
}
|
||||
void pop(size_t n) { m_read = modulo(m_read + n); } ///< Move reading pointer forward.
|
||||
size_t size() const { return modulo(m_write - m_read); }
|
||||
private:
|
||||
static size_t modulo(size_t idx) { return idx % SIZE; } ///< Modulo operation with proper rounding (handles slightly "negative" idx as well)
|
||||
float m_buf[SIZE];
|
||||
volatile size_t m_read, m_write; ///< The indices of the next read/write operations. read == write implies that buffer is empty.
|
||||
};
|
||||
|
||||
/// analyzer class
|
||||
/** class to analyze input audio and transform it into useable data
|
||||
*/
|
||||
class Analyzer {
|
||||
public:
|
||||
/// fast fourier transform vector
|
||||
typedef std::vector<std::complex<float> > fft_t;
|
||||
/// list of tones
|
||||
typedef std::list<Tone> tones_t;
|
||||
/// constructor
|
||||
Analyzer(double rate, std::string id, unsigned step = 200);
|
||||
/** Add input data to buffer. This is thread-safe (against other functions). **/
|
||||
template <typename InIt> void input(InIt begin, InIt end) {
|
||||
m_buf.insert(begin, end);
|
||||
m_passthrough.insert(begin, end);
|
||||
}
|
||||
/** Call this to process all data input so far. **/
|
||||
void process();
|
||||
/** Get the raw FFT. **/
|
||||
fft_t const& getFFT() const { return m_fft; }
|
||||
/** Get the peak level in dB (negative value, 0.0 = clipping). **/
|
||||
double getPeak() const { return 10.0 * log10(m_peak); }
|
||||
double getPeakRaw() const { return m_peak; }
|
||||
/** Get a list of all tones detected. **/
|
||||
tones_t const& getTones() const { return m_tones; }
|
||||
/** Find a tone within the singing range; prefers strong tones around 200-400 Hz. **/
|
||||
Tone const* findTone(double minfreq = 65.0, double maxfreq = 1000.0) const {
|
||||
if (m_tones.empty()) { m_oldfreq = 0.0; return NULL; }
|
||||
double db = std::max_element(m_tones.begin(), m_tones.end(), Tone::dbCompare)->db;
|
||||
Tone const* best = NULL;
|
||||
double bestscore = 0;
|
||||
for (tones_t::const_iterator it = m_tones.begin(); it != m_tones.end(); ++it) {
|
||||
if (it->db < db - 20.0 || it->freq < minfreq || it->age < Tone::MINAGE) continue;
|
||||
if (it->freq > maxfreq) break;
|
||||
double score = it->db - std::max(180.0, std::abs(it->freq - 300.0)) / 10.0;
|
||||
if (m_oldfreq != 0.0 && std::fabs(it->freq/m_oldfreq - 1.0) < 0.05) score += 10.0;
|
||||
if (best && bestscore > score) break;
|
||||
best = &*it;
|
||||
bestscore = score;
|
||||
}
|
||||
m_oldfreq = (best ? best->freq : 0.0);
|
||||
return best;
|
||||
}
|
||||
/** Give data away for mic pass-through */
|
||||
bool output(float* begin, float* end, double rate);
|
||||
/** Returns the id (color name) of the mic */
|
||||
std::string const& getId() const { return m_id; }
|
||||
|
||||
private:
|
||||
const unsigned m_step;
|
||||
RingBuffer<4 * FFT_N> m_buf; // Twice the FFT size should give enough room for sliding window and for engine delays
|
||||
RingBuffer<4096> m_passthrough;
|
||||
double m_resampleFactor;
|
||||
double m_resamplePos;
|
||||
double m_rate;
|
||||
std::string m_id;
|
||||
std::vector<float> m_window;
|
||||
fft_t m_fft;
|
||||
std::vector<float> m_fftLastPhase;
|
||||
double m_peak;
|
||||
tones_t m_tones;
|
||||
mutable double m_oldfreq;
|
||||
bool calcFFT();
|
||||
void calcTones();
|
||||
void mergeWithOld(tones_t& tones) const;
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
/** Limit val to range [min, max] **/
|
||||
template <typename T> T clamp(T val, T min = 0, T max = 1) {
|
||||
if (min > max) throw std::logic_error("min > max");
|
||||
if (val < min) return min;
|
||||
if (val > max) return max;
|
||||
return val;
|
||||
}
|
||||
|
||||
template <typename Numeric> struct MinMax {
|
||||
Numeric min, max;
|
||||
explicit MinMax(Numeric min = std::numeric_limits<Numeric>::min(), Numeric max = std::numeric_limits<Numeric>::max()): min(min), max(max) {}
|
||||
bool matches(Numeric val) const { return val >= min && val <= max; }
|
||||
};
|
||||
|
||||
/** A convenient way for getting NaNs **/
|
||||
static inline double getNaN() { return std::numeric_limits<double>::quiet_NaN(); }
|
||||
|
||||
/** A convenient way for getting infs **/
|
||||
static inline double getInf() { return std::numeric_limits<double>::infinity(); }
|
||||
|
||||
static inline bool isPow2(unsigned int val) {
|
||||
if (val == 0) return false;
|
||||
if ((val & (val-1)) == 0) return true; // From Wikipedia: Power_of_two
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline unsigned int nextPow2(unsigned int val) {
|
||||
unsigned int ret = 1;
|
||||
while (ret < val) ret *= 2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline unsigned int prevPow2(unsigned int val) {
|
||||
unsigned int ret = 1;
|
||||
while ((ret*2) < val) ret *= 2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
// This file is part of Vocaluxe.
|
||||
//
|
||||
// Vocaluxe is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Vocaluxe is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include "ptAKF.h"
|
||||
#include <cmath>
|
||||
|
||||
#ifdef USE_FFT
|
||||
#include "FFT/FFT.h"
|
||||
#endif
|
||||
|
||||
static constexpr double BaseToneFrequency = 65.4064; // lowest (half-)tone to analyze (C2 = 65.4064 Hz)
|
||||
static constexpr double HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
|
||||
|
||||
int PtAKF::_InitCount = 0;
|
||||
float* restrict PtAKF::_SamplesPerPeriodPerTone = NULL;
|
||||
float* restrict PtAKF::_SamplesPerPeriodPerToneFine = NULL;
|
||||
float* restrict PtAKF::_Window = NULL;
|
||||
|
||||
double GetFrequencyFromTone(double toneIndex){
|
||||
return BaseToneFrequency * pow(HalftoneBase, toneIndex);
|
||||
}
|
||||
|
||||
PtAKF::PtAKF(unsigned step){
|
||||
_InitCount++;
|
||||
if(_InitCount == 1){
|
||||
_SamplesPerPeriodPerTone = new float[_MaxHalfTone + 1 + _HalfTonesAdd];
|
||||
_SamplesPerPeriodPerToneFine = new float[(_MaxHalfTone + 1 + _HalfTonesAdd) * 2];
|
||||
//Init Array to avoid costly calculations
|
||||
for (int toneIndex = 0; toneIndex <= _MaxHalfTone + _HalfTonesAdd; toneIndex++)
|
||||
{
|
||||
_SamplesPerPeriodPerTone[toneIndex] = static_cast<float>(44100.0 / GetFrequencyFromTone(toneIndex)); // samples in one period
|
||||
_SamplesPerPeriodPerToneFine[2 * toneIndex] = static_cast<float>(44100.0 / GetFrequencyFromTone(toneIndex - 1./3.)); // samples for a bit below exact frequency
|
||||
_SamplesPerPeriodPerToneFine[2 * toneIndex + 1] = static_cast<float>(44100.0 / GetFrequencyFromTone(toneIndex + 1./3.)); // samples for a bit above exact frequency
|
||||
}
|
||||
_Window = new float[_SampleCt];
|
||||
double cosMult = 2. * M_PI / (2 * _SampleCt - 1.); //To simplify and speed up; 2 * _SampleCt because we extend the data by a factor of 2
|
||||
for (int i = 0; i < _SampleCt; i++) {
|
||||
_Window[i] = static_cast<float>(0.54 - 0.46 * cos(i * cosMult));
|
||||
}
|
||||
}
|
||||
_Step = step;
|
||||
_VolTreshold = 0.01f;
|
||||
_LastMaxVol = 0.f;
|
||||
for(int i = 0; i < _SmoothCt; i++)
|
||||
_LastTones[i] = -1;
|
||||
_LastToneIndex = 0;
|
||||
}
|
||||
|
||||
PtAKF::~PtAKF(){
|
||||
_InitCount--;
|
||||
if(!_InitCount){
|
||||
delete[] _SamplesPerPeriodPerTone;
|
||||
_SamplesPerPeriodPerTone = NULL;
|
||||
delete[] _SamplesPerPeriodPerToneFine;
|
||||
_SamplesPerPeriodPerToneFine = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void PtAKF::SetVolumeThreshold(float threshold){
|
||||
_VolTreshold = threshold;
|
||||
}
|
||||
|
||||
int PtAKF::GetNote(float* restrict maxVolume, float* restrict weights){
|
||||
float AnaylsisBuf[_SampleCt];
|
||||
int note = _LastTones[_LastToneIndex];
|
||||
*maxVolume = _LastMaxVol * 0.85f;
|
||||
if(_AnalysisBuf.read(AnaylsisBuf, AnaylsisBuf + _SampleCt)){
|
||||
do{
|
||||
_AnalysisBuf.pop(_Step);
|
||||
note = _GetNote(AnaylsisBuf, maxVolume, weights);
|
||||
if(++_LastToneIndex >= _SmoothCt)
|
||||
_LastToneIndex = 0;
|
||||
_LastTones[_LastToneIndex] = note;
|
||||
}while(_AnalysisBuf.read(AnaylsisBuf, AnaylsisBuf + _SampleCt));
|
||||
note = _GetSmoothTone();
|
||||
}
|
||||
return note;
|
||||
}
|
||||
|
||||
int PtAKF::_GetSmoothTone(){
|
||||
int tones[_SmoothCt];
|
||||
int ct = 0;
|
||||
for(int i=0; i < _SmoothCt; i++){
|
||||
int j;
|
||||
int tone = _LastTones[i];
|
||||
if(tone < 0)
|
||||
continue;
|
||||
for(j = ct; j > 0; j--){
|
||||
if(tones[j-1] > tone)
|
||||
tones[j] = tones[j-1];
|
||||
else break;
|
||||
}
|
||||
tones[j] = tone;
|
||||
ct++;
|
||||
}
|
||||
if(ct == 0)
|
||||
return -1; //Nothing detected
|
||||
if(ct % 2 == 0)
|
||||
return (tones[ct/2] + tones[ct/2-1]) / 2; //For even cts get the mean of the 2 middle tones
|
||||
return tones[ct/2]; //For odd cts get the median (middle tone)
|
||||
}
|
||||
|
||||
void PtAKF::InitPeaks(SPeak peaks[_MaxPeaks]){
|
||||
for(int i = 0; i < _MaxPeaks; i++){
|
||||
peaks[i].toneIndex = -1;
|
||||
peaks[i].weight = -9.f;
|
||||
}
|
||||
}
|
||||
|
||||
void PtAKF::AddPeak(SPeak peaks[], float curWeight, int toneIndex){
|
||||
if(curWeight > peaks[0].weight){
|
||||
int i;
|
||||
for(i = 1; i < _MaxPeaks; i++){
|
||||
if(curWeight < peaks[i].weight){
|
||||
peaks[i-1].toneIndex = toneIndex;
|
||||
peaks[i-1].weight = curWeight;
|
||||
break;
|
||||
}else{
|
||||
peaks[i-1] = peaks[i];
|
||||
}
|
||||
}
|
||||
if(i == _MaxPeaks){
|
||||
peaks[i-1].toneIndex = toneIndex;
|
||||
peaks[i-1].weight = curWeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int PtAKF::_GetNote(float samples[_SampleCt], float* restrict maxVolume, float weights[_MaxHalfTone+1]){
|
||||
// Calculate maximum volume
|
||||
|
||||
float maxVolumeL = 0;
|
||||
for(int i = _SampleCt / 2; i < _SampleCt; i++){
|
||||
float vol = abs(samples[i]);
|
||||
if(vol > maxVolumeL)
|
||||
maxVolumeL = vol;
|
||||
}
|
||||
//maxVolumeL /= 32767.f;
|
||||
*maxVolume = maxVolumeL;
|
||||
|
||||
if(maxVolumeL < _VolTreshold)
|
||||
return -1;
|
||||
|
||||
float samplesWindowed[_SampleCt * 2];
|
||||
|
||||
for(int i = 0; i < _SampleCt; i++){
|
||||
samplesWindowed[i] = samples[i] * _Window[i];
|
||||
}
|
||||
|
||||
#ifdef USE_FFT
|
||||
for(int i = _SampleCt; i < _SampleCt * 2; i++){
|
||||
samplesWindowed[i] = 0.f;
|
||||
}
|
||||
float samplesFFT[_SampleCt+1]; // +1 for middle value!
|
||||
PowerSpectrum(_SampleCt*2, samplesWindowed, samplesFFT);
|
||||
RealInverseRealFFT(_SampleCt*2, samplesFFT, _AKFValues);
|
||||
#endif
|
||||
// Now analyze the samples and get peaks at the most appropriate tones
|
||||
|
||||
//Attention: We have a peak at lag 0 that might stretch that far, that we detect a wrong "peak" at _MaxHalfTone
|
||||
//Because of that we filter out all tones that are past the last zero crossing from below but keep tones with decreasing weights (going towards zero crossing from above)
|
||||
int lastValidTone = 0;
|
||||
float lastWeight = 1.f;
|
||||
float maxWeight = 0.f;
|
||||
|
||||
//TODO: When using FFT this can be speed up a lot by only checking around the AKF peaks
|
||||
#pragma omp parallel for schedule(static)
|
||||
for (int toneIndex = 0; toneIndex <= _MaxHalfTone; toneIndex++){
|
||||
float curWeight = _AnalyzeByTone(samples, samplesWindowed, toneIndex);
|
||||
|
||||
weights[toneIndex] = curWeight;
|
||||
}
|
||||
|
||||
for (int toneIndex = 0; toneIndex <= _MaxHalfTone; toneIndex++){
|
||||
float curWeight = weights[toneIndex];
|
||||
if(curWeight > maxWeight){
|
||||
maxWeight = curWeight;
|
||||
}
|
||||
if(lastWeight > curWeight || (lastWeight > 0.f && curWeight <= 0.f))
|
||||
lastValidTone = toneIndex - 1;
|
||||
lastWeight = curWeight;
|
||||
}
|
||||
|
||||
// Now clear off the lag 0 peak if required
|
||||
|
||||
if(maxWeight >= weights[_MaxHalfTone]){
|
||||
//We might have caught the lag 0 peak so go a bit further to check for other zero crossings (or we won't be able to detect _MaxHalfTone)
|
||||
float lastWeight = _AKFByTone(samples, _MaxHalfTone);
|
||||
for(int toneIndex = _MaxHalfTone+1; toneIndex <= _MaxHalfTone + _HalfTonesAdd; toneIndex++){
|
||||
float curWeight = _AKFByTone(samples, toneIndex);
|
||||
if(lastWeight > curWeight || (lastWeight > 0.f && curWeight <= 0.f)){
|
||||
lastValidTone = toneIndex - 1;
|
||||
break;
|
||||
}
|
||||
lastWeight = curWeight;
|
||||
}
|
||||
if(lastValidTone < 0)
|
||||
return -1;
|
||||
//Set all invalid weights to 0
|
||||
for(int toneIndex = lastValidTone + 1;toneIndex <= _MaxHalfTone; toneIndex++){
|
||||
weights[toneIndex] = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
SPeak peaks[_MaxPeaks];
|
||||
InitPeaks(peaks);
|
||||
int numPeaks = 0;
|
||||
bool up = true;
|
||||
for(int i = 1; i <= _MaxHalfTone; i++){
|
||||
if(weights[i - 1] > weights[i]){
|
||||
if(up){
|
||||
AddPeak(peaks, weights[i - 1], i - 1);
|
||||
numPeaks++;
|
||||
up = false;
|
||||
}
|
||||
}else if(!up){
|
||||
up = true;
|
||||
}
|
||||
}
|
||||
if(up && weights[_MaxHalfTone] > 0.001f){
|
||||
AddPeak(peaks, weights[_MaxHalfTone], _MaxHalfTone);
|
||||
numPeaks++;
|
||||
}
|
||||
if(numPeaks > _MaxPeaks)
|
||||
numPeaks = _MaxPeaks;
|
||||
|
||||
// Further analyze the found peaks and find the real maximum
|
||||
// This is necessary because we may have our real peak a bit off the exact tone frequency
|
||||
// and a 'wrong' peak that is exactly at another tone which might become higher than the one at the 'right' tone
|
||||
|
||||
maxWeight = peaks[_MaxPeaks-1].weight;
|
||||
if(maxWeight < 0.00001f) // Just a small number to check for zero values
|
||||
return -1;
|
||||
int maxTone = peaks[_MaxPeaks-1].toneIndex;
|
||||
int maxToneFine = -1;
|
||||
for(int i = _MaxPeaks - numPeaks; i < _MaxPeaks; i++){
|
||||
float curWeight = peaks[i].weight;
|
||||
if(curWeight * 3.f < maxWeight)
|
||||
continue;
|
||||
int toneIndex = peaks[i].toneIndex;
|
||||
float curWeightDown = _AnalyzeBySampleCt(samples, samplesWindowed, _SamplesPerPeriodPerToneFine[toneIndex * 2]);
|
||||
int otherToneIndex;
|
||||
int otherToneFineIndex;
|
||||
if(curWeightDown > curWeight){
|
||||
otherToneIndex = toneIndex - 1;
|
||||
otherToneFineIndex = 1;
|
||||
curWeight = curWeightDown;
|
||||
}else{
|
||||
float curWeightUp = _AnalyzeBySampleCt(samples, samplesWindowed, _SamplesPerPeriodPerToneFine[toneIndex * 2 + 1]);
|
||||
if(curWeightUp > curWeight){
|
||||
otherToneIndex = (toneIndex < _MaxHalfTone) ? toneIndex + 1: -1; // If the other tone is invalid just set it to -1 which will be skipped below
|
||||
otherToneFineIndex = 0;
|
||||
curWeight = curWeightUp;
|
||||
}else
|
||||
continue;
|
||||
}
|
||||
weights[toneIndex] = curWeight;
|
||||
if(curWeight > maxWeight){
|
||||
maxTone = toneIndex;
|
||||
maxToneFine = 1 - otherToneFineIndex;
|
||||
maxWeight = curWeight;
|
||||
}
|
||||
|
||||
// Now check also neighbouring tone
|
||||
if(otherToneIndex >= 0){
|
||||
float curWeightOther = _AnalyzeBySampleCt(samples, samplesWindowed, _SamplesPerPeriodPerToneFine[otherToneIndex * 2 + otherToneFineIndex]);
|
||||
if(curWeightOther > weights[otherToneIndex]){
|
||||
weights[otherToneIndex] = curWeightOther;
|
||||
if(curWeightOther > maxWeight){
|
||||
maxTone = otherToneIndex;
|
||||
maxToneFine = otherToneFineIndex;
|
||||
maxWeight = curWeightOther;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float energy = _AKFBySampleCt(samplesWindowed, 0);
|
||||
float maxAKF = (maxToneFine < 0) ? _AKFByTone(samplesWindowed, maxTone) : _AKFBySampleCt(samplesWindowed, _SamplesPerPeriodPerToneFine[maxTone * 2 + maxToneFine]);
|
||||
|
||||
//if(maxWeight - minWeight > 0.025){
|
||||
if(maxAKF >= 0.33f * energy){
|
||||
return maxTone;
|
||||
}else return -1;
|
||||
}
|
||||
|
||||
float PtAKF::_AnalyzeByTone(float samples[_SampleCt], float samplesWindowed[_SampleCt], int toneIndex){
|
||||
return _AnalyzeBySampleCt(samples, samplesWindowed, _SamplesPerPeriodPerTone[toneIndex]);
|
||||
}
|
||||
|
||||
float PtAKF::_AnalyzeBySampleCt(float samples[_SampleCt], float samplesWindowed[_SampleCt], float samplesPerPeriodD){
|
||||
// Use method by Kobayashi and Shimamura (2001): Combine AKF and AMDF to a new f(z)=AKF(z)/(AMDF(z)+k) with k=1
|
||||
|
||||
float akf = _AKFBySampleCt(samplesWindowed, samplesPerPeriodD);
|
||||
float amdf = _AMDFBySampleCt(samples, samplesPerPeriodD);
|
||||
|
||||
//return accumDistAKF / (accumDistAMDF * 32767.f + 32767.f * 32767.f); // Need to scale AKF by MAX^2 and AMDF by MAX, so do some maths to divide only once
|
||||
float result = akf / (amdf + 1.f);
|
||||
return result; // Need to scale AKF by MAX^2 and AMDF by MAX, so do some maths to divide only once
|
||||
//{toneIndex}: {accumDistAKF} ; {accumDistAMDF}; {result}
|
||||
}
|
||||
|
||||
float PtAKF::_AKFByTone(float samples[_SampleCt], int toneIndex){
|
||||
return _AKFBySampleCt(samples, _SamplesPerPeriodPerTone[toneIndex]);
|
||||
}
|
||||
|
||||
float PtAKF::_AKFBySampleCt(float samples[_SampleCt], float samplesPerPeriodD){
|
||||
int samplesPerPeriod = static_cast<int>(samplesPerPeriodD);
|
||||
float fHigh = samplesPerPeriodD - samplesPerPeriod;
|
||||
float fLow = 1.0f - fHigh;
|
||||
#ifdef USE_FFT
|
||||
float akf2 = _AKFValues[samplesPerPeriod] * fLow + _AKFValues[samplesPerPeriod+1] * fHigh;
|
||||
return akf2 / (_SampleCt * _SampleCt);
|
||||
#else
|
||||
|
||||
float accumDist = 0; // accumulated distances
|
||||
|
||||
// compare correlating samples
|
||||
int sampleIndex = 0; // index of sample to analyze
|
||||
// Start value= index of sample one period ahead
|
||||
for (int correlatingSampleIndex = sampleIndex + samplesPerPeriod; correlatingSampleIndex + 1 < _SampleCt; correlatingSampleIndex++, sampleIndex++)
|
||||
{
|
||||
// calc distance to corresponding sample in next period
|
||||
float xn = samples[sampleIndex];
|
||||
float xnt = samples[correlatingSampleIndex] * fLow + samples[correlatingSampleIndex + 1] * fHigh;
|
||||
accumDist += xn * xnt;
|
||||
}
|
||||
|
||||
return accumDist / _SampleCt;
|
||||
#endif
|
||||
}
|
||||
|
||||
float PtAKF::_AMDFByTone(float samples[_SampleCt], int toneIndex){
|
||||
return _AMDFBySampleCt(samples, _SamplesPerPeriodPerTone[toneIndex]);
|
||||
}
|
||||
|
||||
float PtAKF::_AMDFBySampleCt(float samples[_SampleCt], float samplesPerPeriodD){
|
||||
int samplesPerPeriod = static_cast<int>(samplesPerPeriodD);
|
||||
float fHigh = samplesPerPeriodD - samplesPerPeriod;
|
||||
float fLow = 1.0f - fHigh;
|
||||
|
||||
float accumDist = 0; // accumulated distances
|
||||
|
||||
// compare correlating samples
|
||||
int sampleIndex = 0; // index of sample to analyze
|
||||
// Start value= index of sample one period ahead
|
||||
for (int correlatingSampleIndex = sampleIndex + samplesPerPeriod; correlatingSampleIndex + 1 < _SampleCt; correlatingSampleIndex++, sampleIndex++)
|
||||
{
|
||||
// calc distance to corresponding sample in next period
|
||||
float xn = samples[sampleIndex];
|
||||
float xnt = samples[correlatingSampleIndex] * fLow + samples[correlatingSampleIndex + 1] * fHigh;
|
||||
accumDist += abs(xn - xnt);
|
||||
}
|
||||
|
||||
return accumDist / sampleIndex;
|
||||
}
|
||||
/*
|
||||
|
||||
|
||||
|
||||
namespace PitchTrackerAKF{
|
||||
static double *SamplesPerPeriodPerTone = NULL;
|
||||
static double BaseToneFrequency = -1;
|
||||
static int MinHalfTone = -1, MaxHalfTone = -1;
|
||||
static const double HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
|
||||
|
||||
void Init(double baseToneFrequency, int minHalfTone, int maxHalfTone){
|
||||
if(SamplesPerPeriodPerTone && abs(baseToneFrequency - BaseToneFrequency) < 1. && minHalfTone == MinHalfTone && maxHalfTone == MaxHalfTone)
|
||||
return;
|
||||
BaseToneFrequency = baseToneFrequency;
|
||||
MinHalfTone = minHalfTone;
|
||||
MaxHalfTone = maxHalfTone;
|
||||
DeInit();
|
||||
if(minHalfTone < 0 || maxHalfTone < minHalfTone)
|
||||
return;
|
||||
SamplesPerPeriodPerTone = (double*) malloc((MaxHalfTone + 1) * sizeof(double));
|
||||
//Init Array to avoid costly calculations
|
||||
for (int toneIndex = 0; toneIndex <= MaxHalfTone; toneIndex++)
|
||||
{
|
||||
double freq = baseToneFrequency * pow(HalftoneBase, toneIndex);
|
||||
SamplesPerPeriodPerTone[toneIndex] = 44100.0 / freq; // samples in one period
|
||||
}
|
||||
}
|
||||
|
||||
void DeInit(){
|
||||
if(SamplesPerPeriodPerTone){
|
||||
free(SamplesPerPeriodPerTone);
|
||||
SamplesPerPeriodPerTone=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static T _AnalyzeToneFunc(T *samples, int sampleCt, int toneIndex, bool scale){
|
||||
// Use method by Kobayashi and Shimamura (1995): Combine AKF and AMDF to a new f(z)=AKF(z)/(AMDF(z)+k) with k=1
|
||||
double samplesPerPeriodD = SamplesPerPeriodPerTone[toneIndex]; // samples in one period
|
||||
int samplesPerPeriod = static_cast<int>(samplesPerPeriodD);
|
||||
T fHigh = static_cast<T>(samplesPerPeriodD - samplesPerPeriod);
|
||||
T fLow = static_cast<T>(1.0 - fHigh);
|
||||
|
||||
T accumDistAKF = 0; // accumulated distances
|
||||
T accumDistAMDF = 0; // accumulated distances
|
||||
|
||||
// compare correlating samples
|
||||
int sampleIndex = 0; // index of sample to analyze
|
||||
// Start value= index of sample one period ahead
|
||||
for (int correlatingSampleIndex = sampleIndex + samplesPerPeriod; correlatingSampleIndex + 1 < sampleCt; correlatingSampleIndex++, sampleIndex++)
|
||||
{
|
||||
// calc distance to corresponding sample in next period
|
||||
T xn = samples[sampleIndex];
|
||||
T xnt = samples[correlatingSampleIndex] * fLow + samples[correlatingSampleIndex + 1] * fHigh;
|
||||
accumDistAKF += xn * xnt;
|
||||
accumDistAMDF += abs(xn - xnt);
|
||||
}
|
||||
|
||||
accumDistAKF /= sampleCt;
|
||||
accumDistAMDF /= sampleIndex;
|
||||
|
||||
if(scale)
|
||||
return accumDistAKF / (accumDistAMDF * 32767.f + 32767.f); // Need to scale AKF by MAX^2 and AMDF by MAX, so do some maths to divide only once
|
||||
else
|
||||
return accumDistAKF / (accumDistAMDF + 1.f);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
int GetTone(T *samples, int sampleCt, double* maxVolume, float *weights, bool scale){
|
||||
if(!SamplesPerPeriodPerTone)
|
||||
return -1;
|
||||
T maxVolumeL = 0;
|
||||
for(int i = 0; i < sampleCt; i++){
|
||||
T vol = abs(samples[i]);
|
||||
if(vol > maxVolumeL)
|
||||
maxVolumeL = vol;
|
||||
}
|
||||
if(scale)
|
||||
maxVolumeL /= 32767.;
|
||||
*maxVolume = maxVolumeL;
|
||||
|
||||
T maxWeight = -1;
|
||||
T minWeight = 1;
|
||||
int maxTone = -1;
|
||||
for (int toneIndex = MinHalfTone; toneIndex <= MaxHalfTone; toneIndex++)
|
||||
{
|
||||
T curWeight = _AnalyzeToneFunc<T>(samples, sampleCt, toneIndex, scale);
|
||||
|
||||
if (curWeight > maxWeight)
|
||||
{
|
||||
maxWeight = curWeight;
|
||||
maxTone = toneIndex;
|
||||
}
|
||||
|
||||
if (curWeight < minWeight)
|
||||
minWeight = curWeight;
|
||||
|
||||
weights[toneIndex - MinHalfTone] = static_cast<float>(curWeight);
|
||||
}
|
||||
|
||||
if(maxWeight - minWeight > 0.01){
|
||||
return maxTone;
|
||||
}else return -1;
|
||||
}
|
||||
|
||||
template int GetTone(float *samples, int sampleCt, double* maxVolume, float *weights, bool scale);
|
||||
template int GetTone(double *samples, int sampleCt, double* maxVolume, float *weights, bool scale);
|
||||
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
#include "performous/pitch.hh"
|
||||
|
||||
#if __STDC__ != 1
|
||||
# define restrict __restrict
|
||||
#else
|
||||
# ifndef __STDC_VERSION__
|
||||
# define restrict __restrict
|
||||
# else
|
||||
# if __STDC_VERSION__ < 199901L
|
||||
# define restrict __restrict
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#define USE_FFT
|
||||
|
||||
struct SPeak{
|
||||
int toneIndex;
|
||||
float weight;
|
||||
};
|
||||
|
||||
// A pitch detection that is based on the AKF and AMDF (initially from original Vocaluxe/USDx)
|
||||
class PtAKF{
|
||||
public:
|
||||
PtAKF(unsigned step);
|
||||
~PtAKF();
|
||||
|
||||
/** Add input data to buffer. This is thread-safe (against other functions). **/
|
||||
template <typename InIt> void input(InIt begin, InIt end) {
|
||||
_AnalysisBuf.insert(begin, end);
|
||||
}
|
||||
|
||||
int GetNote(float* restrict maxVolume, float* restrict weights);
|
||||
void SetVolumeThreshold(float threshold);
|
||||
float GetVolumeThreshold(){return _VolTreshold;}
|
||||
static int GetNumHalfTones(){ return _MaxHalfTone + 1;}
|
||||
|
||||
private:
|
||||
static int _InitCount;
|
||||
static float* restrict _SamplesPerPeriodPerTone;
|
||||
// Use a 3 times finer resolution for exact peak detection
|
||||
// So this array will store for each note the number of samples 1/3 below and up
|
||||
// (cannot use halves as it would be ambiguous)
|
||||
static float* restrict _SamplesPerPeriodPerToneFine;
|
||||
static float* restrict _Window;
|
||||
//static constexpr int _MinHalfTone = 0; //C2
|
||||
static constexpr int _MaxHalfTone = 56;//47; //B5
|
||||
static constexpr int _HalfTonesAdd = 4; //Additonal half tones to analyze to remove the peak at lag 0
|
||||
static constexpr int _MaxPeaks = 10;
|
||||
static constexpr int _SmoothCt = 3; //Number of samples used for smoothing the result
|
||||
|
||||
constexpr static size_t _SampleCt = 2048;
|
||||
RingBuffer<_SampleCt * 2> _AnalysisBuf;
|
||||
unsigned _Step;
|
||||
float _VolTreshold;
|
||||
float _LastMaxVol;
|
||||
int _LastTones[_SmoothCt];
|
||||
int _LastToneIndex;
|
||||
#ifdef USE_FFT
|
||||
float _AKFValues[_SampleCt*2];
|
||||
#endif
|
||||
|
||||
int _GetNote(float samples[_SampleCt], float* restrict maxVolume, float weights[_MaxHalfTone+1]);
|
||||
int _GetSmoothTone();
|
||||
|
||||
float _AnalyzeBySampleCt(float samples[_SampleCt], float samplesWindowed[_SampleCt], float samplesPerPeriodD);
|
||||
inline float _AnalyzeByTone(float samples[_SampleCt], float samplesWindowed[_SampleCt], int toneIndex);
|
||||
float _AKFBySampleCt(float samples[_SampleCt], float samplesPerPeriodD);
|
||||
inline float _AKFByTone(float samples[_SampleCt], int toneIndex);
|
||||
static float _AMDFBySampleCt(float samples[_SampleCt], float samplesPerPeriodD);
|
||||
static inline float _AMDFByTone(float samples[_SampleCt], int toneIndex);
|
||||
static inline void InitPeaks(SPeak peaks[_MaxPeaks]);
|
||||
static inline void AddPeak(SPeak peaks[_MaxPeaks], float curWeight, int toneIndex);
|
||||
};
|
||||
@@ -9,6 +9,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vocaluxe", "Vocaluxe\Vocalu
|
||||
{EC5167E4-CCFB-4CEE-987C-CF816F1062A3} = {EC5167E4-CCFB-4CEE-987C-CF816F1062A3}
|
||||
{D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC} = {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}
|
||||
{5A4053F1-5AD5-441F-B254-D97A088127B6} = {5A4053F1-5AD5-441F-B254-D97A088127B6}
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A} = {54A48AF7-515C-4EDD-B395-2BFFA096050A}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VocaluxeLib", "VocaluxeLib\VocaluxeLib.csproj", "{D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}"
|
||||
@@ -25,6 +26,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerLib", "ClientServerLi
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebserverInitalConfig", "WebserverInitalConfig\WebserverInitalConfig.csproj", "{55F89808-4E79-4FFE-BC13-48258913F5C9}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PitchTracker", "PitchTracker\PitchTracker.vcxproj", "{54A48AF7-515C-4EDD-B395-2BFFA096050A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
DebugLinux|x64 = DebugLinux|x64
|
||||
@@ -192,6 +195,26 @@ Global
|
||||
{55F89808-4E79-4FFE-BC13-48258913F5C9}.ReleaseWin|x64.Build.0 = Release|Any CPU
|
||||
{55F89808-4E79-4FFE-BC13-48258913F5C9}.ReleaseWin|x86.ActiveCfg = Release|Any CPU
|
||||
{55F89808-4E79-4FFE-BC13-48258913F5C9}.ReleaseWin|x86.Build.0 = Release|Any CPU
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugLinux|x64.ActiveCfg = Debug|x64
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugLinux|x64.Build.0 = Debug|x64
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugLinux|x86.ActiveCfg = Debug|Win32
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugLinux|x86.Build.0 = Debug|Win32
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugWin|x64.ActiveCfg = Debug|x64
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugWin|x64.Build.0 = Debug|x64
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugWin|x86.ActiveCfg = Debug|Win32
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugWin|x86.Build.0 = Debug|Win32
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.InstallerWin|x64.ActiveCfg = Release|x64
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.InstallerWin|x64.Build.0 = Release|x64
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.InstallerWin|x86.ActiveCfg = Release|Win32
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.InstallerWin|x86.Build.0 = Release|Win32
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseLinux|x64.ActiveCfg = Release|x64
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseLinux|x64.Build.0 = Release|x64
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseLinux|x86.ActiveCfg = Release|Win32
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseLinux|x86.Build.0 = Release|Win32
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseWin|x64.ActiveCfg = Release|x64
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseWin|x64.Build.0 = Release|x64
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseWin|x86.ActiveCfg = Release|Win32
|
||||
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseWin|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -38,9 +38,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Objekte verwerfen, bevor Bereich verloren geht", Scope = "member", Target = "Vocaluxe.Lib.Draw.CDirect3D.#CopyScreen()")
|
||||
]
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Scope = "type", Target = "Vocaluxe.Lib.Input.WiiMote.InputReport")]
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Scope = "type", Target = "PortAudioSharp.PortAudio+PaDeviceIndex")]
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Scope = "type", Target = "PortAudioSharp.PortAudio+PaSampleFormat")]
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Scope = "type", Target = "PortAudioSharp.PortAudio+PaStreamCallbackFlags")]
|
||||
[assembly:
|
||||
SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member",
|
||||
Target = "Vocaluxe.Lib.Video.Gstreamer.CGstreamerVideoWrapper.#GetFrame(System.Int32,System.Single)")]
|
||||
@@ -49,19 +46,4 @@ using System.Diagnostics.CodeAnalysis;
|
||||
Target = "Vocaluxe.Lib.Input.CHIDAPI.#ReadTimeout(System.IntPtr,System.Byte[]&,System.Int32,System.Int32)")]
|
||||
[assembly:
|
||||
SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member",
|
||||
Target = "Vocaluxe.Lib.Input.CHIDAPI.#Read(System.IntPtr,System.Byte[]&,System.Int32)")]
|
||||
[assembly: SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "PortAudioSharp.PortAudio.#Pa_GetVersionText()")]
|
||||
[assembly:
|
||||
SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member",
|
||||
Target = "PortAudioSharp.PortAudio.#Pa_GetErrorText(PortAudioSharp.PortAudio+PaError)")]
|
||||
[assembly:
|
||||
SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member",
|
||||
Target = "PortAudioSharp.PortAudio.#Pa_GetHostApiInfo(System.Int32)")]
|
||||
[assembly:
|
||||
SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "PortAudioSharp.PortAudio.#Pa_GetLastHostErrorInfo()")]
|
||||
[assembly:
|
||||
SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member",
|
||||
Target = "PortAudioSharp.PortAudio.#Pa_GetDeviceInfo(System.Int32)")]
|
||||
[assembly:
|
||||
SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member",
|
||||
Target = "PortAudioSharp.PortAudio.#Pa_GetStreamInfo(System.IntPtr)")]
|
||||
Target = "Vocaluxe.Lib.Input.CHIDAPI.#Read(System.IntPtr,System.Byte[]&,System.Int32)")]
|
||||
@@ -16,6 +16,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using PortAudioSharp;
|
||||
using Vocaluxe.Base;
|
||||
@@ -25,39 +26,52 @@ namespace Vocaluxe.Lib.Sound
|
||||
/// <summary>
|
||||
/// PortAudio can be used for record and playback
|
||||
/// So do some common stuff here and make sure those 2 do not interfere
|
||||
/// Basic lifetime: On Init() get a new handle, close/dispose it in your close/dispose
|
||||
/// DO NEVER use following Pa_* functions other than the ones from this class:
|
||||
/// Initialize, Terminate, OpenStream, CloseStream
|
||||
/// </summary>
|
||||
abstract class CPortAudioCommon
|
||||
class CPortAudioHandle : IDisposable
|
||||
{
|
||||
private static int _RefCount;
|
||||
private static readonly object _Mutex = new object();
|
||||
|
||||
private bool _Disposed;
|
||||
private readonly List<IntPtr> _Streams = new List<IntPtr>();
|
||||
|
||||
/// <summary>
|
||||
/// Safe method to init PortAudio (adds a reference)
|
||||
/// MUST call CloseDriver when done
|
||||
/// Initializes PortAudio library (if required)
|
||||
/// </summary>
|
||||
/// <returns>True on success, falso if not initialized (log written)</returns>
|
||||
protected static bool _InitDriver()
|
||||
public CPortAudioHandle()
|
||||
{
|
||||
lock (_Mutex)
|
||||
{
|
||||
if (_RefCount == 0)
|
||||
{
|
||||
if (_CheckError("Initialize", PortAudio.Pa_Initialize()))
|
||||
return false;
|
||||
if (CheckError("Initialize", PortAudio.Pa_Initialize()))
|
||||
throw new Exception();
|
||||
}
|
||||
_RefCount++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safe method to close PortAudio
|
||||
/// MUST be called _exactly_ once after successfull InitDriver call
|
||||
/// </summary>
|
||||
protected static void _CloseDriver()
|
||||
~CPortAudioHandle()
|
||||
{
|
||||
_Dispose(false);
|
||||
}
|
||||
|
||||
private void _Dispose(bool disposing)
|
||||
{
|
||||
if (_Disposed)
|
||||
return;
|
||||
if (!disposing)
|
||||
CLog.LogDebug("Did not close CPortAudioHandle");
|
||||
//Make sure we do not leek any streams as we may keep PA open
|
||||
if (_Streams.Count > 0)
|
||||
{
|
||||
CLog.LogDebug("Did not close " + _Streams.Count + "PortAudio-Stream(s)");
|
||||
while (_Streams.Count > 0)
|
||||
CloseStream(_Streams[0]);
|
||||
}
|
||||
lock (_Mutex)
|
||||
{
|
||||
Debug.Assert(_RefCount > 0);
|
||||
@@ -65,15 +79,37 @@ namespace Vocaluxe.Lib.Sound
|
||||
if (_RefCount == 0)
|
||||
PortAudio.Pa_Terminate();
|
||||
}
|
||||
_Disposed = true;
|
||||
}
|
||||
|
||||
protected static PortAudio.PaError _OpenStream(out IntPtr stream, ref PortAudio.PaStreamParameters? inputParameters, ref PortAudio.PaStreamParameters? outputParameters,
|
||||
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
||||
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
||||
public void Dispose()
|
||||
{
|
||||
_Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close the PortAudio handle once you are done
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public PortAudio.PaError OpenStream(out IntPtr stream, ref PortAudio.PaStreamParameters? inputParameters, ref PortAudio.PaStreamParameters? outputParameters,
|
||||
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
||||
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
||||
{
|
||||
if (_Disposed)
|
||||
throw new ObjectDisposedException("PortAudioHandle already disposed");
|
||||
|
||||
lock (_Mutex)
|
||||
{
|
||||
return PortAudio.Pa_OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData);
|
||||
PortAudio.PaError res = PortAudio.Pa_OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback,
|
||||
userData);
|
||||
if (res == PortAudio.PaError.paNoError)
|
||||
_Streams.Add(stream);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,14 +124,14 @@ namespace Vocaluxe.Lib.Sound
|
||||
/// <param name="streamCallback"></param>
|
||||
/// <param name="userData"></param>
|
||||
/// <returns>True on success</returns>
|
||||
protected static bool _OpenInputStream(out IntPtr stream, ref PortAudio.PaStreamParameters? inputParameters,
|
||||
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
||||
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
||||
public bool OpenInputStream(out IntPtr stream, ref PortAudio.PaStreamParameters? inputParameters,
|
||||
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
||||
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
||||
{
|
||||
PortAudio.PaStreamParameters? outputParameters = null;
|
||||
return
|
||||
!_CheckError("OpenInputStream",
|
||||
_OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData));
|
||||
!CheckError("OpenInputStream",
|
||||
OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -109,21 +145,25 @@ namespace Vocaluxe.Lib.Sound
|
||||
/// <param name="streamCallback"></param>
|
||||
/// <param name="userData"></param>
|
||||
/// <returns>True on success</returns>
|
||||
protected static bool _OpenOutputStream(out IntPtr stream, ref PortAudio.PaStreamParameters? outputParameters,
|
||||
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
||||
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
||||
public bool OpenOutputStream(out IntPtr stream, ref PortAudio.PaStreamParameters? outputParameters,
|
||||
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
||||
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
||||
{
|
||||
PortAudio.PaStreamParameters? inputParameters = null;
|
||||
return
|
||||
!_CheckError("OpenOutputStream",
|
||||
_OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData));
|
||||
!CheckError("OpenOutputStream",
|
||||
OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData));
|
||||
}
|
||||
|
||||
protected static void _CloseStream(IntPtr stream)
|
||||
public void CloseStream(IntPtr stream)
|
||||
{
|
||||
if (_Disposed)
|
||||
throw new ObjectDisposedException("PortAudioHandle already disposed");
|
||||
|
||||
lock (_Mutex)
|
||||
{
|
||||
PortAudio.Pa_CloseStream(stream);
|
||||
_Streams.Remove(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,8 +174,11 @@ namespace Vocaluxe.Lib.Sound
|
||||
/// <param name="action">Action identifier (E.g. openStream)</param>
|
||||
/// <param name="errorCode">Result returned by Pa_* call</param>
|
||||
/// <returns>True on error</returns>
|
||||
protected static bool _CheckError(String action, PortAudio.PaError errorCode)
|
||||
public bool CheckError(String action, PortAudio.PaError errorCode)
|
||||
{
|
||||
if (_Disposed)
|
||||
throw new ObjectDisposedException("PortAudioHandle already disposed");
|
||||
|
||||
if (errorCode != PortAudio.PaError.paNoError)
|
||||
{
|
||||
CLog.LogError(action + " error: " + PortAudio.Pa_GetErrorText(errorCode));
|
||||
@@ -156,11 +199,10 @@ namespace Vocaluxe.Lib.Sound
|
||||
/// Selects the most appropriate host api
|
||||
/// </summary>
|
||||
/// <returns>The most appropriate host api</returns>
|
||||
protected static int _GetHostApi()
|
||||
public int GetHostApi()
|
||||
{
|
||||
//Caller has to hold a reference anyway so no locking needed
|
||||
if (_RefCount < 1)
|
||||
return -1;
|
||||
if (_Disposed)
|
||||
throw new ObjectDisposedException("PortAudioHandle already disposed");
|
||||
|
||||
int selectedHostApi = PortAudio.Pa_GetDefaultHostApi();
|
||||
int apiCount = PortAudio.Pa_GetHostApiCount();
|
||||
@@ -25,13 +25,14 @@ using VocaluxeLib;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
{
|
||||
class CPortAudioStream : CPortAudioCommon, IDisposable
|
||||
class CPortAudioStream : IDisposable
|
||||
{
|
||||
private const long _Bufsize = 1000000L;
|
||||
private const long _Beginrefill = 800000L;
|
||||
|
||||
public readonly int ID;
|
||||
private readonly Closeproc _Closeproc;
|
||||
private CPortAudioHandle _PaHandle;
|
||||
|
||||
private readonly CSyncTimer _SyncTimer;
|
||||
private int _ByteCount = 4;
|
||||
@@ -207,12 +208,12 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
{
|
||||
Paused = false;
|
||||
_AfterFadeAction = EStreamAction.Nothing;
|
||||
_CheckError("StartStream", PortAudioSharp.PortAudio.Pa_StartStream(_Stream));
|
||||
_PaHandle.CheckError("StartStream", PortAudioSharp.PortAudio.Pa_StartStream(_Stream));
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_CheckError("StopStream (playback)", PortAudioSharp.PortAudio.Pa_StopStream(_Stream));
|
||||
_PaHandle.CheckError("StopStream (playback)", PortAudioSharp.PortAudio.Pa_StopStream(_Stream));
|
||||
Skip(0f);
|
||||
}
|
||||
|
||||
@@ -230,14 +231,11 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
if (!File.Exists(fileName))
|
||||
return false;
|
||||
|
||||
bool driverInitialized = false;
|
||||
try
|
||||
{
|
||||
if (!_InitDriver())
|
||||
return false;
|
||||
driverInitialized = true;
|
||||
_PaHandle = new CPortAudioHandle();
|
||||
|
||||
int hostApi = _GetHostApi();
|
||||
int hostApi = _PaHandle.GetHostApi();
|
||||
_ApiInfo = PortAudioSharp.PortAudio.Pa_GetHostApiInfo(hostApi);
|
||||
_OutputDeviceInfo = PortAudioSharp.PortAudio.Pa_GetDeviceInfo(_ApiInfo.defaultOutputDevice);
|
||||
if (_OutputDeviceInfo.defaultLowOutputLatency < 0.1)
|
||||
@@ -247,8 +245,11 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (driverInitialized)
|
||||
_CloseDriver();
|
||||
if (_PaHandle != null)
|
||||
{
|
||||
_PaHandle.Close();
|
||||
_PaHandle = null;
|
||||
}
|
||||
CLog.LogError("Error Init PortAudio Playback");
|
||||
return false;
|
||||
}
|
||||
@@ -260,7 +261,8 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
SFormatInfo format = _Decoder.GetFormatInfo();
|
||||
if (format.SamplesPerSecond == 0)
|
||||
{
|
||||
_CloseDriver();
|
||||
_PaHandle.Close();
|
||||
_PaHandle = null;
|
||||
_Decoder.Close();
|
||||
_Decoder = null;
|
||||
CLog.LogError("Error Init PortAudio Playback (samples=0)");
|
||||
@@ -282,7 +284,7 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
hostApiSpecificStreamInfo = IntPtr.Zero
|
||||
};
|
||||
|
||||
if (!_OpenOutputStream(
|
||||
if (!_PaHandle.OpenOutputStream(
|
||||
out _Stream,
|
||||
ref outputParams,
|
||||
format.SamplesPerSecond,
|
||||
@@ -291,7 +293,8 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
_PaStreamCallback,
|
||||
IntPtr.Zero) || _Stream == IntPtr.Zero)
|
||||
{
|
||||
_CloseDriver();
|
||||
_PaHandle.Close();
|
||||
_PaHandle = null;
|
||||
_Decoder.Close();
|
||||
_Decoder = null;
|
||||
return false;
|
||||
@@ -430,9 +433,11 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
||||
|
||||
private void _DoFree()
|
||||
{
|
||||
_CloseStream(_Stream);
|
||||
_CloseDriver();
|
||||
_PaHandle.CloseStream(_Stream);
|
||||
_PaHandle.Close();
|
||||
_PaHandle = null;
|
||||
_Decoder.Close();
|
||||
_Decoder = null;
|
||||
|
||||
_Closeproc(ID);
|
||||
}
|
||||
|
||||
@@ -15,48 +15,42 @@
|
||||
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
|
||||
#endregion
|
||||
|
||||
//#define TEST_PITCH
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Vocaluxe.Lib.Sound.Record.PitchTracker;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record
|
||||
{
|
||||
class CBuffer : IDisposable
|
||||
{
|
||||
//Half tones: C C♯ D Eb E F F♯ G G♯ A Bb B
|
||||
private const double _BaseToneFreq = 65.4064; // lowest (half-)tone to analyze (C2 = 65.4064 Hz)
|
||||
public const int NumHalfTones = 17; //Number of halftone to analyze C2-E3 (TODO: use more/full octaves?)
|
||||
private CPitchTracker _PitchTracker = new CPtAKF();
|
||||
|
||||
private const int _AnalysisBufLen = 4096;
|
||||
private const int _AnalysisByteBufLen = _AnalysisBufLen * 2;
|
||||
|
||||
private readonly Int16[] _AnalysisBuffer = new Int16[_AnalysisBufLen];
|
||||
private readonly byte[] _AnalysisByteBuffer = new byte[_AnalysisByteBufLen]; //tmp buffer (stream->tmpBuffer->(Int16)AnalysisBuffer)
|
||||
|
||||
private static readonly int[] _SamplesPerPeriodPerTone = new int[NumHalfTones]; //Precalculated table holding sample# per period for each tone
|
||||
private readonly float[] _TmpWeight = new float[NumHalfTones]; //tmp buffer for weights (gets copied to ToneWeight when checked)
|
||||
|
||||
private double _MaxVolume;
|
||||
private bool _NewSamples;
|
||||
|
||||
private MemoryStream _Stream = new MemoryStream(); // full buffer
|
||||
private float _MaxVolume;
|
||||
|
||||
public CBuffer()
|
||||
{
|
||||
MinVolume = 0.02f;
|
||||
ToneWeigth = new float[NumHalfTones];
|
||||
for (int i = 0; i < ToneWeigth.Length; i++)
|
||||
ToneWeigth[i] = 0.99f;
|
||||
VolTreshold = 0.02f;
|
||||
ToneWeigths = new float[GetNumHalfTones()];
|
||||
Reset();
|
||||
#if TEST_PITCH
|
||||
CPitchTrackerTest tester = new CPitchTrackerTest();
|
||||
tester.AddAnalyzer(new CAnalyzer());
|
||||
tester.AddAnalyzer(new CPtAKF());
|
||||
tester.AddAnalyzer(new CPtDyWa());
|
||||
tester.AddAnalyzer(new CPtSharp());
|
||||
tester.RunTest();
|
||||
#endif
|
||||
}
|
||||
|
||||
static CBuffer()
|
||||
~CBuffer()
|
||||
{
|
||||
const double halftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
|
||||
//Init Array to avoid costly calculations
|
||||
for (int toneIndex = 0; toneIndex < NumHalfTones; toneIndex++)
|
||||
{
|
||||
double freq = _BaseToneFreq * Math.Pow(halftoneBase, toneIndex);
|
||||
_SamplesPerPeriodPerTone[toneIndex] = (int)Math.Round(44100.0 / freq); // samples in one period
|
||||
}
|
||||
_Dispose();
|
||||
}
|
||||
|
||||
public int GetNumHalfTones()
|
||||
{
|
||||
return _PitchTracker.GetNumHalfTones();
|
||||
}
|
||||
|
||||
public int ToneAbs { get; private set; }
|
||||
@@ -65,33 +59,29 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
|
||||
public float MaxVolume
|
||||
{
|
||||
get { return (float)_MaxVolume; }
|
||||
get { return _MaxVolume; }
|
||||
}
|
||||
|
||||
public bool ToneValid { get; private set; }
|
||||
|
||||
public float[] ToneWeigth { get; private set; }
|
||||
public float[] ToneWeigths { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum volume for a tone to be valid
|
||||
/// </summary>
|
||||
public float MinVolume { get; set; }
|
||||
public float VolTreshold
|
||||
{
|
||||
get { return _PitchTracker.VolumeTreshold; }
|
||||
set { _PitchTracker.VolumeTreshold = value; }
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
lock (_Stream)
|
||||
{
|
||||
_Stream.SetLength(0L);
|
||||
_NewSamples = false;
|
||||
}
|
||||
ToneValid = false;
|
||||
ToneAbs = 0;
|
||||
Tone = 0;
|
||||
}
|
||||
|
||||
private void _Add(byte[] bytes)
|
||||
{
|
||||
_Stream.Write(bytes, 0, bytes.Length);
|
||||
for (int i = 0; i < ToneWeigths.Length; i++)
|
||||
ToneWeigths[i] = 0f;
|
||||
}
|
||||
|
||||
public void ProcessNewBuffer(byte[] buffer)
|
||||
@@ -102,132 +92,35 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
// voice passthrough (send data to playback-device)
|
||||
//if (assigned(fVoiceStream)) then
|
||||
//fVoiceStream.WriteData(Buffer, BufferSize);
|
||||
lock (_Stream)
|
||||
{
|
||||
_Add(buffer);
|
||||
_NewSamples = true;
|
||||
}
|
||||
_PitchTracker.Input(buffer);
|
||||
}
|
||||
|
||||
public void AnalyzeBuffer()
|
||||
{
|
||||
if (!_NewSamples)
|
||||
return;
|
||||
|
||||
lock (_Stream)
|
||||
int tone = _PitchTracker.GetNote(out _MaxVolume, ToneWeigths);
|
||||
if (tone >= 0)
|
||||
{
|
||||
if (_Stream.Length >= _AnalysisByteBufLen)
|
||||
{
|
||||
_Stream.Position -= _AnalysisByteBufLen;
|
||||
_Stream.Read(_AnalysisByteBuffer, 0, _AnalysisByteBufLen);
|
||||
}
|
||||
_NewSamples = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _AnalysisBufLen; i++)
|
||||
_AnalysisBuffer[i] = BitConverter.ToInt16(_AnalysisByteBuffer, i * 2);
|
||||
|
||||
try
|
||||
{
|
||||
// find maximum volume
|
||||
_FindMaxVolume();
|
||||
|
||||
if (MaxVolume >= MinVolume)
|
||||
_AnalyzeByAutocorrelation();
|
||||
else
|
||||
ToneValid = false;
|
||||
}
|
||||
catch (Exception) {}
|
||||
}
|
||||
|
||||
private void _FindMaxVolume()
|
||||
{
|
||||
short maxVolume = 0;
|
||||
for (int i = 0; i < _AnalysisBuffer.Length / 4; i++)
|
||||
{
|
||||
short volume = _AnalysisBuffer[i];
|
||||
if (volume < 0)
|
||||
{
|
||||
if (volume == Int16.MinValue)
|
||||
{
|
||||
maxVolume = Int16.MaxValue;
|
||||
break;
|
||||
}
|
||||
volume = (short)-volume;
|
||||
}
|
||||
if (volume > maxVolume)
|
||||
maxVolume = volume;
|
||||
}
|
||||
_MaxVolume = (double)maxVolume / Int16.MaxValue;
|
||||
}
|
||||
|
||||
private void _AnalyzeByAutocorrelation()
|
||||
{
|
||||
// prepare to analyze
|
||||
double maxWeight = -1.0;
|
||||
double minWeight = 1.0;
|
||||
int maxTone = -1;
|
||||
|
||||
// analyze halftones
|
||||
// Note: at the lowest tone (~65Hz) and a buffer-size of 4096
|
||||
// at 44.1 (or 48kHz) only 6 (or 5) samples are compared, this might be
|
||||
// too few samples -> use a bigger buffer-size
|
||||
for (int toneIndex = 0; toneIndex < NumHalfTones; toneIndex++)
|
||||
{
|
||||
double curWeight = _AnalyzeAutocorrelationTone(toneIndex);
|
||||
|
||||
if (curWeight > maxWeight)
|
||||
{
|
||||
maxWeight = curWeight;
|
||||
maxTone = toneIndex;
|
||||
}
|
||||
|
||||
if (curWeight < minWeight)
|
||||
minWeight = curWeight;
|
||||
|
||||
_TmpWeight[toneIndex] = (float)curWeight;
|
||||
}
|
||||
|
||||
if (maxWeight - minWeight > 0.01)
|
||||
{
|
||||
Array.Copy(_TmpWeight, ToneWeigth, NumHalfTones);
|
||||
|
||||
ToneAbs = maxTone;
|
||||
Tone = maxTone % 12;
|
||||
ToneAbs = tone;
|
||||
Tone = ToneAbs % 12;
|
||||
ToneValid = true;
|
||||
}
|
||||
else
|
||||
ToneValid = false;
|
||||
}
|
||||
|
||||
private double _AnalyzeAutocorrelationTone(int tone)
|
||||
{
|
||||
int samplesPerPeriod = _SamplesPerPeriodPerTone[tone]; // samples in one period
|
||||
|
||||
int accumDist = 0; // accumulated distances
|
||||
|
||||
// compare correlating samples
|
||||
int sampleIndex = 0; // index of sample to analyze
|
||||
// Start value= index of sample one period ahead
|
||||
for (int correlatingSampleIndex = sampleIndex + samplesPerPeriod; correlatingSampleIndex < _AnalysisBufLen; correlatingSampleIndex++, sampleIndex++)
|
||||
{
|
||||
// calc distance (correlation: 1-dist/IntMax) to corresponding sample in next period
|
||||
// distance (0=equal .. IntMax=totally different) between correlated samples
|
||||
int dist = Math.Abs(_AnalysisBuffer[sampleIndex] - _AnalysisBuffer[correlatingSampleIndex]);
|
||||
accumDist += dist;
|
||||
}
|
||||
|
||||
return 1.0 - (double)accumDist / Int16.MaxValue / sampleIndex;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_Stream != null)
|
||||
{
|
||||
_Stream.Dispose();
|
||||
_Stream = null;
|
||||
}
|
||||
_Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void _Dispose()
|
||||
{
|
||||
if (_PitchTracker != null)
|
||||
{
|
||||
_PitchTracker.Dispose();
|
||||
_PitchTracker = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Vocaluxe.Base;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class to implement common functionality for Record classes
|
||||
/// </summary>
|
||||
abstract class CRecordBase
|
||||
{
|
||||
private bool _Initialized;
|
||||
protected List<CRecordDevice> _Devices;
|
||||
protected CBuffer[] _Buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Init recording
|
||||
/// Child class should overwrite this to list devices
|
||||
/// </summary>
|
||||
/// <returns>true if success</returns>
|
||||
public virtual bool Init()
|
||||
{
|
||||
if (_Initialized)
|
||||
return false;
|
||||
|
||||
_Devices = new List<CRecordDevice>();
|
||||
_Buffer = new CBuffer[CSettings.MaxNumPlayer];
|
||||
for (int i = 0; i < _Buffer.Length; i++)
|
||||
_Buffer[i] = new CBuffer();
|
||||
|
||||
_Initialized = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop all voice capturing streams and terminates recording freeing resources
|
||||
/// </summary>
|
||||
public virtual void Close()
|
||||
{
|
||||
if (!_Initialized)
|
||||
return;
|
||||
|
||||
_Devices = null;
|
||||
if (_Buffer != null)
|
||||
{
|
||||
foreach (CBuffer buffer in _Buffer)
|
||||
buffer.Dispose();
|
||||
_Buffer = null;
|
||||
}
|
||||
|
||||
_Initialized = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Procedure to be called by descendent classes with data that got recorded
|
||||
/// </summary>
|
||||
/// <param name="device">Device from which the data was recorded</param>
|
||||
/// <param name="data">Recorded samples, assume Int16 and interleaved for multi-channels</param>
|
||||
protected void _HandleData(CRecordDevice device, byte[] data)
|
||||
{
|
||||
byte[] leftBuffer;
|
||||
byte[] rightBuffer;
|
||||
|
||||
if (device.Channels == 2)
|
||||
{
|
||||
leftBuffer = new byte[data.Length / 2];
|
||||
rightBuffer = new byte[data.Length / 2];
|
||||
//[]: Sample, L: Left channel R: Right channel
|
||||
//[LR][LR][LR][LR][LR][LR]
|
||||
//The data is interleaved and needs to be demultiplexed
|
||||
for (int i = 0; i < data.Length / 4; i++)
|
||||
{
|
||||
leftBuffer[i * 2] = data[i * 4];
|
||||
leftBuffer[i * 2 + 1] = data[i * 4 + 1];
|
||||
rightBuffer[i * 2] = data[i * 4 + 2];
|
||||
rightBuffer[i * 2 + 1] = data[i * 4 + 3];
|
||||
}
|
||||
}
|
||||
else
|
||||
leftBuffer = rightBuffer = data;
|
||||
|
||||
if (device.PlayerChannel1 > 0)
|
||||
_Buffer[device.PlayerChannel1 - 1].ProcessNewBuffer(leftBuffer);
|
||||
|
||||
if (device.PlayerChannel2 > 0)
|
||||
_Buffer[device.PlayerChannel2 - 1].ProcessNewBuffer(rightBuffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect Pitch and Volume of the newest voice buffer
|
||||
/// </summary>
|
||||
/// <param name="player"></param>
|
||||
public void AnalyzeBuffer(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return;
|
||||
|
||||
_Buffer[player].AnalyzeBuffer();
|
||||
}
|
||||
|
||||
public int GetToneAbs(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return 0;
|
||||
|
||||
return _Buffer[player].ToneAbs;
|
||||
}
|
||||
|
||||
public int GetTone(int player)
|
||||
{
|
||||
return _Initialized ? _Buffer[player].Tone : 0;
|
||||
}
|
||||
|
||||
public void SetTone(int player, int tone)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return;
|
||||
|
||||
_Buffer[player].Tone = tone;
|
||||
}
|
||||
|
||||
public float GetMaxVolume(int player)
|
||||
{
|
||||
return _Initialized ? _Buffer[player].MaxVolume : 0f;
|
||||
}
|
||||
|
||||
public bool ToneValid(int player)
|
||||
{
|
||||
return _Initialized && _Buffer[player].ToneValid;
|
||||
}
|
||||
|
||||
public int NumHalfTones()
|
||||
{
|
||||
return _Buffer[0].GetNumHalfTones();
|
||||
}
|
||||
|
||||
public float[] ToneWeigth(int player)
|
||||
{
|
||||
return _Initialized ? _Buffer[player].ToneWeigths : null;
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<CRecordDevice> RecordDevices()
|
||||
{
|
||||
return (_Initialized && _Devices.Count > 0) ? _Devices.AsReadOnly() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,39 +23,28 @@ using Vocaluxe.Base;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
||||
{
|
||||
class CDirectSoundRecord : IRecord
|
||||
class CDirectSoundRecord : CRecordBase, IRecord
|
||||
{
|
||||
private bool _Initialized;
|
||||
private List<CRecordDevice> _Devices;
|
||||
|
||||
private List<CSoundCardSource> _Sources;
|
||||
|
||||
private readonly CBuffer[] _Buffer;
|
||||
|
||||
public CDirectSoundRecord()
|
||||
public override bool Init()
|
||||
{
|
||||
_Buffer = new CBuffer[CSettings.MaxNumPlayer];
|
||||
for (int i = 0; i < _Buffer.Length; i++)
|
||||
_Buffer[i] = new CBuffer();
|
||||
if (!base.Init())
|
||||
return false;
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public bool Init()
|
||||
{
|
||||
DeviceCollection devices = DirectSoundCapture.GetDevices();
|
||||
_Devices = new List<CRecordDevice>();
|
||||
_Sources = new List<CSoundCardSource>();
|
||||
|
||||
int id = 0;
|
||||
DeviceCollection devices = DirectSoundCapture.GetDevices();
|
||||
|
||||
foreach (DeviceInformation dev in devices)
|
||||
{
|
||||
using (var ds = new DirectSoundCapture(dev.DriverGuid))
|
||||
{
|
||||
var device = new CRecordDevice(id, dev.DriverGuid.ToString(), dev.Description, ds.Capabilities.Channels);
|
||||
var device = new CRecordDevice(_Devices.Count, dev.DriverGuid.ToString(), dev.Description, ds.Capabilities.Channels);
|
||||
|
||||
_Devices.Add(device);
|
||||
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,14 +53,11 @@ namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
public override void Close()
|
||||
{
|
||||
if (_Initialized)
|
||||
{
|
||||
Stop();
|
||||
_Initialized = false;
|
||||
}
|
||||
//System.IO.File.WriteAllBytes("test0.raw", _Buffer[0].Buffer);
|
||||
Stop();
|
||||
_Initialized = false;
|
||||
base.Close();
|
||||
}
|
||||
|
||||
public bool Start()
|
||||
@@ -112,81 +98,6 @@ namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AnalyzeBuffer(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return;
|
||||
|
||||
_Buffer[player].AnalyzeBuffer();
|
||||
}
|
||||
|
||||
public int GetToneAbs(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return 0;
|
||||
|
||||
return _Buffer[player].ToneAbs;
|
||||
}
|
||||
|
||||
public int GetTone(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return 0;
|
||||
|
||||
return _Buffer[player].Tone;
|
||||
}
|
||||
|
||||
public void SetTone(int player, int tone)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return;
|
||||
|
||||
_Buffer[player].Tone = tone;
|
||||
}
|
||||
|
||||
public float GetMaxVolume(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return 0f;
|
||||
|
||||
return _Buffer[player].MaxVolume;
|
||||
}
|
||||
|
||||
public bool ToneValid(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return false;
|
||||
|
||||
return _Buffer[player].ToneValid;
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<CRecordDevice> RecordDevices()
|
||||
{
|
||||
if (!_Initialized)
|
||||
return null;
|
||||
|
||||
if (_Devices.Count == 0)
|
||||
return null;
|
||||
|
||||
return _Devices.AsReadOnly();
|
||||
}
|
||||
|
||||
public int NumHalfTones()
|
||||
{
|
||||
if (!_Initialized)
|
||||
return 0;
|
||||
|
||||
return CBuffer.NumHalfTones;
|
||||
}
|
||||
|
||||
public float[] ToneWeigth(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return null;
|
||||
|
||||
return _Buffer[player].ToneWeigth;
|
||||
}
|
||||
|
||||
private void _OnDataReady(object sender, CSampleDataEventArgs e)
|
||||
{
|
||||
if (!_Initialized)
|
||||
@@ -194,32 +105,7 @@ namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
||||
CRecordDevice dev = _Devices.FirstOrDefault(device => device.Driver == e.Guid);
|
||||
if (dev == null)
|
||||
return;
|
||||
byte[] leftBuffer;
|
||||
byte[] rightBuffer;
|
||||
|
||||
if (dev.Channels == 2)
|
||||
{
|
||||
leftBuffer = new byte[e.Data.Length / 2];
|
||||
rightBuffer = new byte[e.Data.Length / 2];
|
||||
//[]: Sample, L: Left channel R: Right channel
|
||||
//[LR][LR][LR][LR][LR][LR]
|
||||
//The data is interleaved and needs to be demultiplexed
|
||||
for (int i = 0; i < e.Data.Length / 4; i++)
|
||||
{
|
||||
leftBuffer[i * 2] = e.Data[i * 4];
|
||||
leftBuffer[i * 2 + 1] = e.Data[i * 4 + 1];
|
||||
rightBuffer[i * 2] = e.Data[i * 4 + 2];
|
||||
rightBuffer[i * 2 + 1] = e.Data[i * 4 + 3];
|
||||
}
|
||||
}
|
||||
else
|
||||
leftBuffer = rightBuffer = e.Data;
|
||||
|
||||
if (dev.PlayerChannel1 > 0)
|
||||
_Buffer[dev.PlayerChannel1 - 1].ProcessNewBuffer(leftBuffer);
|
||||
|
||||
if (dev.PlayerChannel2 > 0)
|
||||
_Buffer[dev.PlayerChannel2 - 1].ProcessNewBuffer(rightBuffer);
|
||||
_HandleData(dev, e.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzer from Performous
|
||||
/// Should be very reliable but needs some proof (artificial tests show some errors)
|
||||
/// </summary>
|
||||
class CAnalyzer : CPitchTracker
|
||||
{
|
||||
#region Imports
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern IntPtr Analyzer_Create(uint step = 200);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void Analyzer_Free(IntPtr analyzer);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void Analyzer_InputFloat(IntPtr analyzer, [In] float[] data, int sampleCt);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void Analyzer_InputShort(IntPtr analyzer, [In] short[] data, int sampleCt);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void Analyzer_InputByte(IntPtr analyzer, [In] byte[] data, int sampleCt);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void Analyzer_Process(IntPtr analyzer);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern float Analyzer_GetPeak(IntPtr analyzer);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern double Analyzer_FindNote(IntPtr analyzer, double minFreq, double maxFreq);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool Analyzer_OutputFloat(IntPtr analyzer, [Out] float[] data, int sampleCt, float rate);
|
||||
#endregion
|
||||
|
||||
private IntPtr _Instance;
|
||||
private float _VolumeTreshold;
|
||||
|
||||
public CAnalyzer(uint step = 200)
|
||||
{
|
||||
_Instance = Analyzer_Create(step);
|
||||
_VolumeTreshold = 0.01f;
|
||||
}
|
||||
|
||||
public override void Input(byte[] data)
|
||||
{
|
||||
Analyzer_InputByte(_Instance, data, data.Length / 2);
|
||||
}
|
||||
|
||||
public override int GetNote(out float maxVolume, float[] weights)
|
||||
{
|
||||
Analyzer_Process(_Instance);
|
||||
maxVolume = Analyzer_GetPeak(_Instance);
|
||||
if (maxVolume < _VolumeTreshold)
|
||||
return -1;
|
||||
int note = (int)Math.Round(Analyzer_FindNote(_Instance, 60, 1800));
|
||||
_SetWeights(note, weights);
|
||||
return note;
|
||||
}
|
||||
|
||||
public bool Output(float[] data, float rate)
|
||||
{
|
||||
return Analyzer_OutputFloat(_Instance, data, data.Length, rate);
|
||||
}
|
||||
|
||||
protected override void _Dispose(bool disposing)
|
||||
{
|
||||
if (_Instance == IntPtr.Zero)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
Analyzer_Free(_Instance);
|
||||
_Instance = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public override int GetNumHalfTones()
|
||||
{
|
||||
return NumHalfTonesDef;
|
||||
}
|
||||
|
||||
public override float VolumeTreshold
|
||||
{
|
||||
get { return _VolumeTreshold; }
|
||||
set { _VolumeTreshold = value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using VocaluxeLib;
|
||||
using VocaluxeLib.Utils;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker
|
||||
{
|
||||
class CPitchTrackerTest
|
||||
{
|
||||
//Half tones: C C# D D# E F F# G G# A A# B
|
||||
private const double _BaseToneFreq = 65.4064; // lowest (half-)tone to analyze (C2 = 65.4064 Hz)
|
||||
private const double _HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
|
||||
|
||||
private readonly List<CPitchTracker> _Analyzers = new List<CPitchTracker>();
|
||||
private int[] _Tones;
|
||||
private float[][] _Weights;
|
||||
private float _MaxVolume;
|
||||
private int _CurTestCount;
|
||||
private int[] _CurPassedCount;
|
||||
private int _TestCount;
|
||||
private int[] _PassedCount;
|
||||
private int[] _SamplesPerSec;
|
||||
|
||||
private static bool _IsRun;
|
||||
|
||||
public void AddAnalyzer(CPitchTracker analyzer)
|
||||
{
|
||||
_Analyzers.Add(analyzer);
|
||||
}
|
||||
|
||||
#region Init
|
||||
private void _InitTests()
|
||||
{
|
||||
_Tones = new int[_Analyzers.Count];
|
||||
_TestCount = 0;
|
||||
_PassedCount = new int[_Analyzers.Count];
|
||||
_CurTestCount = 0;
|
||||
_CurPassedCount = new int[_Analyzers.Count];
|
||||
_Weights = new float[_Analyzers.Count][];
|
||||
for (int i = 0; i < _Analyzers.Count; i++)
|
||||
_Weights[i] = new float[_Analyzers[i].GetNumHalfTones()];
|
||||
_SamplesPerSec = new int[_Analyzers.Count];
|
||||
}
|
||||
|
||||
private void _InitNext(string lastTest)
|
||||
{
|
||||
string msg = "Errors " + lastTest + ": ";
|
||||
for (int i = 0; i < _CurPassedCount.Length; i++)
|
||||
{
|
||||
msg += _CurTestCount - _CurPassedCount[i] + " ";
|
||||
_PassedCount[i] += _CurPassedCount[i];
|
||||
_CurPassedCount[i] = 0;
|
||||
}
|
||||
msg += "of " + _CurTestCount;
|
||||
CBase.Log.LogDebug(msg);
|
||||
_TestCount += _CurTestCount;
|
||||
_CurTestCount = 0;
|
||||
//Do a reset first as we actually have an impossible situation (drop by multiple octaves)
|
||||
byte[] data = new byte[4096 * 2];
|
||||
_Process(data);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void RunTest(bool reRun = false)
|
||||
{
|
||||
if (_IsRun && !reRun)
|
||||
return;
|
||||
_IsRun = true;
|
||||
|
||||
_InitTests();
|
||||
|
||||
_TestSines();
|
||||
_InitNext("Sines");
|
||||
|
||||
_TestFile("toneG3.wav", 19);
|
||||
_InitNext("Real G3");
|
||||
|
||||
_TestFile("toneG3Miss.wav", 19);
|
||||
_InitNext("Real G3 with miss. fundamental");
|
||||
|
||||
_TestFile("toneG4.wav", 31);
|
||||
_InitNext("Real G4");
|
||||
|
||||
_TestFile("whistling3.wav", "whistling3.txt");
|
||||
_InitNext("High whistling");
|
||||
|
||||
_TestFile("sClausVoc.wav", "sClausVoc.txt");
|
||||
_InitNext("Real song");
|
||||
|
||||
_TestSpeed();
|
||||
CBase.Log.LogDebug("Finished: ");
|
||||
for (int i = 0; i < _Analyzers.Count; i++)
|
||||
{
|
||||
string msg = _Analyzers[i].GetType().Name + ":";
|
||||
msg += " Errors=" + (_TestCount - _PassedCount[i]);
|
||||
msg += " Passed=" + _PassedCount[i];
|
||||
msg += " Total=" + _TestCount;
|
||||
msg += " Speed=" + _SamplesPerSec[i] / 1000 + "kSamples/s (=" + (_SamplesPerSec[i] / 44100) + "rec.s/s)";
|
||||
CBase.Log.LogDebug(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void _TestSpeed()
|
||||
{
|
||||
const int samplesPerBuffer = 512;
|
||||
byte[] data;
|
||||
byte[] data2 = new byte[samplesPerBuffer * 2];
|
||||
double angle = 0;
|
||||
const int repeats = 100;
|
||||
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, 5), 44100, samplesPerBuffer * repeats, ref angle, out data);
|
||||
for (int i = 0; i < _Analyzers.Count; i++)
|
||||
{
|
||||
CPitchTracker analyzer = _Analyzers[i];
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
for (int j = 0; j < repeats; j++)
|
||||
{
|
||||
Buffer.BlockCopy(data, 0, data2, 0, samplesPerBuffer * 2);
|
||||
analyzer.Input(data2);
|
||||
analyzer.GetNote(out _MaxVolume, _Weights[i]);
|
||||
}
|
||||
sw.Stop();
|
||||
_SamplesPerSec[i] = (int)Math.Round(samplesPerBuffer * repeats / (sw.ElapsedMilliseconds / 1000.0));
|
||||
}
|
||||
}
|
||||
|
||||
private void _Process(byte[] data)
|
||||
{
|
||||
for (int j = 0; j < _Analyzers.Count; j++)
|
||||
{
|
||||
_Analyzers[j].Input(data);
|
||||
_Tones[j] = _Analyzers[j].GetNote(out _MaxVolume, _Weights[j]);
|
||||
}
|
||||
}
|
||||
|
||||
private static string _ToneToNote(int tone, bool withOctave = true)
|
||||
{
|
||||
if (tone < 0)
|
||||
return "inv.";
|
||||
tone += 24;
|
||||
string[] notes = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
||||
string result = notes[tone % 12];
|
||||
if (withOctave)
|
||||
result += (tone / 12);
|
||||
return result;
|
||||
}
|
||||
|
||||
#region Sines
|
||||
private void _TestSines()
|
||||
{
|
||||
const int toneFrom = 0;
|
||||
const int toneTo = 47; //B5
|
||||
const int sampleCt = 4096;
|
||||
const int batchCt = 512;
|
||||
byte[] data2 = new byte[batchCt * 2];
|
||||
Console.WriteLine("Testing notes " + _ToneToNote(toneFrom) + " - " + _ToneToNote(toneTo));
|
||||
double angle = 0;
|
||||
bool[] valids = new bool[_Analyzers.Count];
|
||||
for (int distort = 0; distort < 10; distort++)
|
||||
{
|
||||
//Do a reset first as we actually have an impossible situation (drop by multiple octaves)
|
||||
byte[] data = new byte[sampleCt * 2];
|
||||
_Process(data);
|
||||
for (int tone = toneFrom; tone <= toneTo; tone++)
|
||||
{
|
||||
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone), 44100, sampleCt, ref angle, out data);
|
||||
if (tone == 46 && distort == 4)
|
||||
data = new byte[data.Length];
|
||||
_Distort(data, tone, distort);
|
||||
|
||||
for (int i = 0; i < sampleCt / batchCt; i++)
|
||||
{
|
||||
Buffer.BlockCopy(data, i * batchCt * 2, data2, 0, batchCt * 2);
|
||||
_Process(data2);
|
||||
if (i * batchCt < 2048)
|
||||
continue;
|
||||
_CurTestCount++;
|
||||
bool ok = true;
|
||||
for (int j = 0; j < valids.Length; j++)
|
||||
{
|
||||
valids[j] = _Tones[j] == tone;
|
||||
if (!valids[j])
|
||||
ok = false;
|
||||
else
|
||||
_CurPassedCount[j]++;
|
||||
}
|
||||
if (ok)
|
||||
continue;
|
||||
string msg = "Note " + _ToneToNote(tone) + "(" + distort + ") at buffer " + (i + 1) + "/" + (sampleCt / batchCt) + " detected as ";
|
||||
for (int j = 0; j < valids.Length; j++)
|
||||
msg += _ToneToNote(_Tones[j]) + (valids[j] ? "" : "(!)") + "; ";
|
||||
CBase.Log.LogDebug(msg);
|
||||
/*CWavFile w = new CWavFile();
|
||||
w.Create(tone + "-" + distort + ".wav", 1, 44100, 16);
|
||||
w.Write16BitSamples(data);
|
||||
w.Close();*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static short[] _GetDistort(int sampleCt, int tone, int type)
|
||||
{
|
||||
short[] sdata = new short[sampleCt];
|
||||
if (type < 9)
|
||||
{
|
||||
int newTone = 1;
|
||||
switch (type)
|
||||
{
|
||||
case 0:
|
||||
newTone = 2;
|
||||
break;
|
||||
case 1:
|
||||
newTone = 3;
|
||||
break;
|
||||
case 2:
|
||||
newTone = 6;
|
||||
break;
|
||||
case 3:
|
||||
newTone = 8;
|
||||
break;
|
||||
case 4:
|
||||
newTone = 10;
|
||||
break;
|
||||
case 5:
|
||||
newTone = 15;
|
||||
break;
|
||||
case 6:
|
||||
newTone = 25;
|
||||
break;
|
||||
case 7:
|
||||
newTone = 26;
|
||||
break;
|
||||
case 8:
|
||||
newTone = 29;
|
||||
break;
|
||||
}
|
||||
byte[] data2;
|
||||
double angle = 0;
|
||||
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone + newTone), 44100, sampleCt, ref angle, out data2);
|
||||
Buffer.BlockCopy(data2, 0, sdata, 0, sampleCt * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
Random r = new Random(0xBEEF);
|
||||
for (int i = 0; i < sdata.Length; i++)
|
||||
sdata[i] = (short)(r.Next() - Int16.MinValue);
|
||||
}
|
||||
return sdata;
|
||||
}
|
||||
|
||||
private static void _Distort(byte[] data, int tone, int distortCt)
|
||||
{
|
||||
if (distortCt < 1)
|
||||
return;
|
||||
short[] sdata = new short[data.Length / 2];
|
||||
Buffer.BlockCopy(data, 0, sdata, 0, data.Length);
|
||||
|
||||
short[][] distortions = new short[distortCt][];
|
||||
for (int i = 0; i < distortCt; i++)
|
||||
distortions[i] = _GetDistort(data.Length / 2, tone, i);
|
||||
|
||||
for (int i = 0; i < sdata.Length; i++)
|
||||
{
|
||||
double distortion = 0;
|
||||
for (int j = 0; j < distortCt; j++)
|
||||
distortion += distortions[j][i];
|
||||
distortion /= distortCt;
|
||||
sdata[i] = (short)(sdata[i] * 4.0 / 5.0 + distortion / 5.0);
|
||||
}
|
||||
Buffer.BlockCopy(sdata, 0, data, 0, data.Length);
|
||||
}
|
||||
|
||||
private static void _GetSineWave(double freq, int sampleRate, int sampleCt, ref double angle, out byte[] data)
|
||||
{
|
||||
const short max = short.MaxValue;
|
||||
short[] data16Bit = new short[sampleCt];
|
||||
for (int i = 0; i < sampleCt; i++)
|
||||
data16Bit[i] = (short)(Math.Sin(2 * Math.PI * i / sampleRate * freq + angle) * max);
|
||||
angle = 2 * Math.PI * sampleCt / sampleRate * freq + angle;
|
||||
angle = angle % (2 * Math.PI);
|
||||
data = new byte[data16Bit.Length * 2];
|
||||
Buffer.BlockCopy(data16Bit, 0, data, 0, data.Length);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region File
|
||||
private struct STimedNote
|
||||
{
|
||||
public int Time, Note;
|
||||
}
|
||||
|
||||
private void _TestFile(string fileName, string testFileName)
|
||||
{
|
||||
if (!File.Exists(testFileName))
|
||||
return;
|
||||
List<STimedNote> tones = new List<STimedNote>();
|
||||
using (StreamReader reader = new StreamReader(testFileName))
|
||||
{
|
||||
String line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
int p = line.IndexOf(' ');
|
||||
if (p < 0)
|
||||
continue;
|
||||
STimedNote note;
|
||||
note.Time = int.Parse(line.Substring(0, p));
|
||||
note.Note = int.Parse(line.Substring(p + 1));
|
||||
tones.Add(note);
|
||||
}
|
||||
}
|
||||
_TestFile(fileName, tones);
|
||||
}
|
||||
|
||||
private void _TestFile(string fileName, int tone)
|
||||
{
|
||||
STimedNote note;
|
||||
note.Note = tone;
|
||||
note.Time = 46;
|
||||
List<STimedNote> tones = new List<STimedNote> {note};
|
||||
_TestFile(fileName, tones);
|
||||
}
|
||||
|
||||
private static bool _IsNoteValid(int note, int time, IList<STimedNote> tones)
|
||||
{
|
||||
const int lastNoteMaxTimeDiff = 1536 * 1000 / 44100; // old note is valid for 1536 more samples
|
||||
for (int i = 0; i < tones.Count; i++)
|
||||
{
|
||||
if (tones[i].Time > time)
|
||||
break;
|
||||
if (i + 1 == tones.Count || time <= tones[i + 1].Time + lastNoteMaxTimeDiff)
|
||||
{
|
||||
if (tones[i].Note == note || tones[i].Note < 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void _TestFile(string fileName, IList<STimedNote> tones)
|
||||
{
|
||||
CWavFile wavFile = new CWavFile();
|
||||
try
|
||||
{
|
||||
if (!wavFile.Open(fileName))
|
||||
return;
|
||||
if (wavFile.BitsPerSample != 16)
|
||||
{
|
||||
wavFile.Close();
|
||||
return;
|
||||
}
|
||||
int samplesRead = 0;
|
||||
int curTimeIndex = -1;
|
||||
int curNote = -1;
|
||||
const int maxSamplesPerBatch = 512;
|
||||
bool[] valids = new bool[_Tones.Length];
|
||||
while (wavFile.NumSamplesLeft > maxSamplesPerBatch)
|
||||
{
|
||||
byte[] samples = wavFile.GetNextSamples16BitAsBytes(maxSamplesPerBatch, 1);
|
||||
samplesRead += samples.Length / 2;
|
||||
int time = samplesRead * 1000 / wavFile.SampleRate;
|
||||
_Process(samples);
|
||||
while (curTimeIndex + 1 < tones.Count && time >= tones[curTimeIndex + 1].Time)
|
||||
{
|
||||
curTimeIndex++;
|
||||
curNote = tones[curTimeIndex].Note;
|
||||
}
|
||||
if (curNote < 0)
|
||||
continue;
|
||||
_CurTestCount++;
|
||||
bool error = false;
|
||||
for (int i = 0; i < _Tones.Length; i++)
|
||||
{
|
||||
valids[i] = _IsNoteValid(_Tones[i], time, tones);
|
||||
if (valids[i])
|
||||
_CurPassedCount[i]++;
|
||||
else
|
||||
error = true;
|
||||
}
|
||||
if (error)
|
||||
{
|
||||
string msg = "Note " + _ToneToNote(curNote) + " at " + time + "ms detected as ";
|
||||
for (int i = 0; i < _Tones.Length; i++)
|
||||
msg += _ToneToNote(_Tones[i]) + (valids[i] ? "" : "(!)") + "; ";
|
||||
CBase.Log.LogDebug(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Error on file " + fileName + ": " + e);
|
||||
}
|
||||
wavFile.Close();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// Pitchtracker (Pt) that uses autocorrelation (AKF) and AMDF
|
||||
/// Quite fast and perfect in artificial tests, but may fail in real world scenarios (e.g. missing fundamental)
|
||||
/// </summary>
|
||||
class CPtAKF : CPitchTracker
|
||||
{
|
||||
#region Imports
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern IntPtr PtAKF_Create(uint step);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void PtAKF_Free(IntPtr analyzer);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern int PtAKF_GetNumHalfTones();
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void PtAKF_InputByte(IntPtr analyzer, [In] byte[] data, int sampleCt);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void PtAKF_SetVolumeThreshold(IntPtr analyzer, float threshold);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern float PtAKF_GetVolumeThreshold(IntPtr analyzer);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern int PtAKF_GetNote(IntPtr analyzer, [Out] out float maxVolume, [Out] float[] weights);
|
||||
#endregion
|
||||
|
||||
private IntPtr _Instance;
|
||||
|
||||
public CPtAKF(uint step = 400)
|
||||
{
|
||||
_Instance = PtAKF_Create(step);
|
||||
}
|
||||
|
||||
public override int GetNumHalfTones()
|
||||
{
|
||||
return PtAKF_GetNumHalfTones();
|
||||
}
|
||||
|
||||
public override float VolumeTreshold
|
||||
{
|
||||
get { return PtAKF_GetVolumeThreshold(_Instance); }
|
||||
set { PtAKF_SetVolumeThreshold(_Instance, value); }
|
||||
}
|
||||
|
||||
public override void Input(byte[] data)
|
||||
{
|
||||
PtAKF_InputByte(_Instance, data, data.Length / 2);
|
||||
}
|
||||
|
||||
public override int GetNote(out float maxVolume, float[] weights)
|
||||
{
|
||||
return PtAKF_GetNote(_Instance, out maxVolume, weights);
|
||||
}
|
||||
|
||||
protected override void _Dispose(bool disposing)
|
||||
{
|
||||
if (_Instance == IntPtr.Zero)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
PtAKF_Free(_Instance);
|
||||
_Instance = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// Pitchtracker (Pt) that uses a dynamic wavelet (DyWa) algorithm
|
||||
/// </summary>
|
||||
class CPtDyWa : CPitchTracker
|
||||
{
|
||||
#region Imports
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern IntPtr PtDyWa_Create(uint step);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void PtDyWa_Free(IntPtr analyzer);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void PtDyWa_SetVolumeThreshold(IntPtr analyzer, float threshold);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern float PtDyWa_GetVolumeThreshold(IntPtr analyzer);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void PtDyWa_InputByte(IntPtr analyzer, [In] byte[] data, int sampleCt);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern double PtDyWa_FindNote(IntPtr analyzer, [Out] out float maxVolume);
|
||||
#endregion
|
||||
|
||||
private IntPtr _Instance;
|
||||
|
||||
public CPtDyWa(uint step = 200)
|
||||
{
|
||||
_Instance = PtDyWa_Create(step);
|
||||
}
|
||||
|
||||
public override void Input(byte[] data)
|
||||
{
|
||||
PtDyWa_InputByte(_Instance, data, data.Length / 2);
|
||||
}
|
||||
|
||||
public override int GetNote(out float maxVolume, float[] weights)
|
||||
{
|
||||
int note = (int)Math.Round(PtDyWa_FindNote(_Instance, out maxVolume));
|
||||
_SetWeights(note, weights);
|
||||
return note;
|
||||
}
|
||||
|
||||
public override int GetNumHalfTones()
|
||||
{
|
||||
return NumHalfTonesDef;
|
||||
}
|
||||
|
||||
public override float VolumeTreshold
|
||||
{
|
||||
get { return PtDyWa_GetVolumeThreshold(_Instance); }
|
||||
set { PtDyWa_SetVolumeThreshold(_Instance, value); }
|
||||
}
|
||||
|
||||
protected override void _Dispose(bool disposing)
|
||||
{
|
||||
if (_Instance == IntPtr.Zero)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
PtDyWa_Free(_Instance);
|
||||
_Instance = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// Pitchtracker (Pt) in C# that uses IIR filters for smoothing and an intelligent detection algorithm to reduce calculations
|
||||
/// DO NOT USE in 32 Bit! Due to floating point errors the filter will raise all values to NAN (TODO: FIX it!)
|
||||
/// </summary>
|
||||
class CPtSharp : CPitchTracker
|
||||
{
|
||||
private Pitch.PitchTracker _Instance;
|
||||
|
||||
public CPtSharp()
|
||||
{
|
||||
_Instance = new Pitch.PitchTracker {SampleRate = 44100, DetectLevelThreshold = 0.01f, PitchRecordHistorySize = 1};
|
||||
}
|
||||
|
||||
public override void Input(byte[] data)
|
||||
{
|
||||
short[] samplesShort = new short[data.Length / 2];
|
||||
Buffer.BlockCopy(data, 0, samplesShort, 0, data.Length);
|
||||
float[] samplesFloat = new float[samplesShort.Length];
|
||||
for (int i = 0; i < samplesShort.Length; i++)
|
||||
samplesFloat[i] = (float)samplesShort[i] / short.MaxValue;
|
||||
_Instance.ProcessBuffer(samplesFloat);
|
||||
}
|
||||
|
||||
public override int GetNote(out float maxVolume, float[] weights)
|
||||
{
|
||||
int note = _Instance.CurrentPitchRecord.MidiNote - 15 - 21;
|
||||
maxVolume = _Instance.LastMaxVol;
|
||||
_SetWeights(note, weights);
|
||||
return note;
|
||||
}
|
||||
|
||||
public override int GetNumHalfTones()
|
||||
{
|
||||
return NumHalfTonesDef;
|
||||
}
|
||||
|
||||
public override float VolumeTreshold
|
||||
{
|
||||
get { return _Instance.DetectLevelThreshold; }
|
||||
set { _Instance.DetectLevelThreshold = value; }
|
||||
}
|
||||
|
||||
protected override void _Dispose(bool disposing)
|
||||
{
|
||||
if (_Instance == null)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
_Instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class/Interface for pitch detectors
|
||||
/// Most likely some structures or system resources are used, hence it should be disposed if no longer needed
|
||||
/// </summary>
|
||||
abstract class CPitchTracker : IDisposable
|
||||
{
|
||||
//Default number of half tones (1 octave)
|
||||
protected const int NumHalfTonesDef = 12;
|
||||
|
||||
~CPitchTracker()
|
||||
{
|
||||
_Dispose(false);
|
||||
}
|
||||
|
||||
protected abstract void _Dispose(bool disposing);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to set weights if the algorithm does not provide anything that could simulate a visualizer.
|
||||
/// Should be called with each valid detection
|
||||
/// </summary>
|
||||
/// <param name="note">Current note</param>
|
||||
/// <param name="weights">Array of weights (Length = NumHalfTonesDef)</param>
|
||||
protected static void _SetWeights(int note, float[] weights)
|
||||
{
|
||||
Debug.Assert(weights.Length == NumHalfTonesDef);
|
||||
for (int i = 0; i < NumHalfTonesDef; i++)
|
||||
weights[i] *= 0.9f;
|
||||
if (note < 0)
|
||||
return;
|
||||
int w = note % NumHalfTonesDef;
|
||||
weights[w] = Math.Min(1f, weights[w] + 0.15f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of half tones the algorithm provides weights for.
|
||||
/// This may not be the number of half tones the algorithm detects!
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract int GetNumHalfTones();
|
||||
|
||||
/// <summary>
|
||||
/// The treshold for detecting silence
|
||||
/// Value (0-1) for which everything below is considered silence
|
||||
/// </summary>
|
||||
public abstract float VolumeTreshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Recorded data. Has to be 16 bit short values.
|
||||
/// </summary>
|
||||
/// <param name="data">16bit short values of data</param>
|
||||
public abstract void Input(byte[] data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current note detected
|
||||
/// </summary>
|
||||
/// <param name="maxVolume">Current maximum volume (0-1)</param>
|
||||
/// <param name="weights">Array of NumHalfTones floats that gets filled with the current weights (0-1) where each value indicates how strong that tone is.
|
||||
/// Only for basic visualization as it may not be accurate.</param>
|
||||
/// <returns>Current note index (0 = C2, 12 = C3, ...)</returns>
|
||||
public abstract int GetNote(out float maxVolume, float[] weights);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker.Pitch
|
||||
{
|
||||
public class CircularBuffer<T> : IDisposable
|
||||
{
|
||||
int m_bufSize;
|
||||
int m_begBufOffset;
|
||||
int m_availBuf;
|
||||
long m_startPos; // total circular buffer position
|
||||
T[] m_buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public CircularBuffer()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="bufCount"></param>
|
||||
public CircularBuffer(int bufCount)
|
||||
{
|
||||
SetSize(bufCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
SetSize(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset to the beginning of the buffer
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
m_begBufOffset = 0;
|
||||
m_availBuf = 0;
|
||||
m_startPos = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the buffer to the specified size
|
||||
/// </summary>
|
||||
/// <param name="newSize"></param>
|
||||
public void SetSize(int newSize)
|
||||
{
|
||||
Reset();
|
||||
|
||||
if (m_bufSize == newSize)
|
||||
return;
|
||||
|
||||
if (m_buffer != null)
|
||||
m_buffer = null;
|
||||
|
||||
m_bufSize = newSize;
|
||||
|
||||
if (m_bufSize > 0)
|
||||
m_buffer = new T[m_bufSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the buffer
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Array.Clear(m_buffer, 0, m_buffer.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the start position
|
||||
/// </summary>
|
||||
public long StartPosition
|
||||
{
|
||||
get { return m_startPos; }
|
||||
set { m_startPos = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the end position
|
||||
/// </summary>
|
||||
public long EndPosition
|
||||
{
|
||||
get { return m_startPos + m_availBuf; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the amount of avaliable space
|
||||
/// </summary>
|
||||
public int Available
|
||||
{
|
||||
get { return m_availBuf; }
|
||||
set { m_availBuf = Math.Min(value, m_bufSize); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write data into the buffer
|
||||
/// </summary>
|
||||
/// <param name="m_pInBuffer"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <returns></returns>
|
||||
public int WriteBuffer(T[] m_pInBuffer, int count)
|
||||
{
|
||||
count = Math.Min(count, m_bufSize);
|
||||
|
||||
var startPos = m_availBuf != m_bufSize ? m_availBuf : m_begBufOffset;
|
||||
var pass1Count = Math.Min(count, m_bufSize - startPos);
|
||||
var pass2Count = count - pass1Count;
|
||||
|
||||
PitchDsp.CopyBuffer(m_pInBuffer, 0, m_buffer, startPos, pass1Count);
|
||||
|
||||
if (pass2Count > 0)
|
||||
PitchDsp.CopyBuffer(m_pInBuffer, pass1Count, m_buffer, 0, pass2Count);
|
||||
|
||||
if (pass2Count == 0)
|
||||
{
|
||||
// did not wrap around
|
||||
if (m_availBuf != m_bufSize)
|
||||
m_availBuf += count; // have never wrapped around
|
||||
else
|
||||
{
|
||||
m_begBufOffset += count;
|
||||
m_startPos += count;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// wrapped around
|
||||
if (m_availBuf != m_bufSize)
|
||||
m_startPos += pass2Count; // first time wrap-around
|
||||
else
|
||||
m_startPos += count;
|
||||
|
||||
m_begBufOffset = pass2Count;
|
||||
m_availBuf = m_bufSize;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read from the buffer
|
||||
/// </summary>
|
||||
/// <param name="outBuffer"></param>
|
||||
/// <param name="startRead"></param>
|
||||
/// <param name="readCount"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadBuffer(T[] outBuffer, long startRead, int readCount)
|
||||
{
|
||||
var endRead = (int)(startRead + readCount);
|
||||
var endAvail = (int)(m_startPos + m_availBuf);
|
||||
|
||||
if (startRead < m_startPos || endRead > endAvail)
|
||||
return false;
|
||||
|
||||
var startReadPos = (int)(((startRead - m_startPos) + m_begBufOffset) % m_bufSize);
|
||||
var block1Samples = Math.Min(readCount, m_bufSize - startReadPos);
|
||||
var block2Samples = readCount - block1Samples;
|
||||
|
||||
PitchDsp.CopyBuffer(m_buffer, startReadPos, outBuffer, 0, block1Samples);
|
||||
|
||||
if (block2Samples > 0)
|
||||
PitchDsp.CopyBuffer(m_buffer, 0, outBuffer, block1Samples, block2Samples);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker.Pitch
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Clear the buffer
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="buffer"></param>
|
||||
public static void Clear(this float[] buffer)
|
||||
{
|
||||
Array.Clear(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the buffer
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="buffer"></param>
|
||||
public static void Clear(this double[] buffer)
|
||||
{
|
||||
Array.Clear(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy the values from one buffer to a different or the same buffer.
|
||||
/// It is safe to copy to the same buffer, even if the areas overlap
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="shiftBy"></param>
|
||||
/// <param name="startIdx"></param>
|
||||
/// <param name="length"></param>
|
||||
public static void Copy(this float[] fromBuffer, float[] toBuffer, int fromStart, int toStart, int length)
|
||||
{
|
||||
if (toBuffer == null || fromBuffer.Length == 0 || toBuffer.Length == 0)
|
||||
return;
|
||||
|
||||
int fromBegIdx = fromStart;
|
||||
int fromEndIdx = fromStart + length;
|
||||
int toBegIdx = toStart;
|
||||
int toEndIdx = toStart + length;
|
||||
|
||||
if (fromBegIdx < 0)
|
||||
{
|
||||
toBegIdx -= fromBegIdx;
|
||||
fromBegIdx = 0;
|
||||
}
|
||||
|
||||
if (toBegIdx < 0)
|
||||
{
|
||||
fromBegIdx -= toBegIdx;
|
||||
toBegIdx = 0;
|
||||
}
|
||||
|
||||
if (fromEndIdx >= fromBuffer.Length)
|
||||
{
|
||||
toEndIdx -= fromEndIdx - fromBuffer.Length + 1;
|
||||
fromEndIdx = fromBuffer.Length - 1;
|
||||
}
|
||||
|
||||
if (toEndIdx >= toBuffer.Length)
|
||||
{
|
||||
fromEndIdx -= toEndIdx - toBuffer.Length + 1;
|
||||
toEndIdx = fromBuffer.Length - 1;
|
||||
}
|
||||
|
||||
if (fromBegIdx < toBegIdx)
|
||||
{
|
||||
// Shift right, so start at the right
|
||||
for (int fromIdx = fromEndIdx, toIdx = toEndIdx; fromIdx >= fromBegIdx; fromIdx--, toIdx--)
|
||||
toBuffer[toIdx] = fromBuffer[fromIdx];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Shift left, so start at the left
|
||||
for (int fromIdx = fromBegIdx, toIdx = toBegIdx; fromIdx <= fromEndIdx; fromIdx++, toIdx++)
|
||||
toBuffer[toIdx] = fromBuffer[fromIdx];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="buffer"></param>
|
||||
public static void Clear(this float[] buffer, int startIdx, int endIdx)
|
||||
{
|
||||
Array.Clear(buffer, startIdx, endIdx - startIdx + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill the buffer with the specified value
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="?"></param>
|
||||
public static void Fill(this double[] buffer, double value)
|
||||
{
|
||||
for (int idx = 0; idx < buffer.Length; idx++)
|
||||
buffer[idx] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
using System;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker.Pitch
|
||||
{
|
||||
/// <summary>
|
||||
/// Infinite impulse response filter (old style analog filters)
|
||||
/// </summary>
|
||||
class IIRFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of filter
|
||||
/// </summary>
|
||||
public enum FilterType
|
||||
{
|
||||
None = 0,
|
||||
LP,
|
||||
HP,
|
||||
BP
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The filter prototype
|
||||
/// </summary>
|
||||
public enum ProtoType
|
||||
{
|
||||
None = 0,
|
||||
Butterworth,
|
||||
Chebyshev,
|
||||
}
|
||||
|
||||
const int kHistMask = 31;
|
||||
const int kHistSize = 32;
|
||||
|
||||
private int m_order;
|
||||
private ProtoType m_protoType;
|
||||
private FilterType m_filterType;
|
||||
|
||||
private float m_fp1;
|
||||
private float m_fp2;
|
||||
private float m_fN;
|
||||
private float m_ripple;
|
||||
private float m_sampleRate;
|
||||
private double[] m_real;
|
||||
private double[] m_imag;
|
||||
private double[] m_z;
|
||||
private double[] m_aCoeff;
|
||||
private double[] m_bCoeff;
|
||||
private double[] m_inHistory;
|
||||
private double[] m_outHistory;
|
||||
private int m_histIdx;
|
||||
private bool m_invertDenormal;
|
||||
|
||||
public IIRFilter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if all the filter parameters are valid
|
||||
/// </summary>
|
||||
public bool FilterValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_order < 1 || m_order > 16 ||
|
||||
m_protoType == ProtoType.None ||
|
||||
m_filterType == FilterType.None ||
|
||||
m_sampleRate <= 0.0f ||
|
||||
m_fN <= 0.0f)
|
||||
return false;
|
||||
|
||||
switch (m_filterType)
|
||||
{
|
||||
case FilterType.LP:
|
||||
if (m_fp2 <= 0.0f)
|
||||
return false;
|
||||
break;
|
||||
|
||||
case FilterType.BP:
|
||||
if (m_fp1 <= 0.0f || m_fp2 <= 0.0f || m_fp1 >= m_fp2)
|
||||
return false;
|
||||
break;
|
||||
|
||||
case FilterType.HP:
|
||||
if (m_fp1 <= 0.0f)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
// For bandpass, the order must be even
|
||||
if (m_filterType == FilterType.BP && (m_order & 1) != 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the filter prototype
|
||||
/// </summary>
|
||||
public ProtoType Proto
|
||||
{
|
||||
get { return m_protoType; }
|
||||
|
||||
set
|
||||
{
|
||||
m_protoType = value;
|
||||
Design();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the filter type
|
||||
/// </summary>
|
||||
public FilterType Type
|
||||
{
|
||||
get { return m_filterType; }
|
||||
|
||||
set
|
||||
{
|
||||
m_filterType = value;
|
||||
Design();
|
||||
}
|
||||
}
|
||||
|
||||
public int Order
|
||||
{
|
||||
get { return m_order; }
|
||||
|
||||
set
|
||||
{
|
||||
m_order = Math.Min(16, Math.Max(1, Math.Abs(value)));
|
||||
|
||||
if (m_filterType == FilterType.BP && Odd(m_order))
|
||||
m_order++;
|
||||
|
||||
Design();
|
||||
}
|
||||
}
|
||||
|
||||
public float SampleRate
|
||||
{
|
||||
get { return m_sampleRate; }
|
||||
|
||||
set
|
||||
{
|
||||
m_sampleRate = value;
|
||||
m_fN = 0.5f * m_sampleRate;
|
||||
Design();
|
||||
}
|
||||
}
|
||||
|
||||
public float FreqLow
|
||||
{
|
||||
get { return m_fp1; }
|
||||
|
||||
set
|
||||
{
|
||||
m_fp1 = value;
|
||||
Design();
|
||||
}
|
||||
}
|
||||
|
||||
public float FreqHigh
|
||||
{
|
||||
get { return m_fp2; }
|
||||
|
||||
set
|
||||
{
|
||||
m_fp2 = value;
|
||||
Design();
|
||||
}
|
||||
}
|
||||
|
||||
public float Ripple
|
||||
{
|
||||
get { return m_ripple; }
|
||||
|
||||
set
|
||||
{
|
||||
m_ripple = value;
|
||||
Design();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if n is odd
|
||||
/// </summary>
|
||||
/// <param name="n"></param>
|
||||
/// <returns></returns>
|
||||
private bool Odd(int n)
|
||||
{
|
||||
return (n & 1) == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Square
|
||||
/// </summary>
|
||||
/// <param name="f"></param>
|
||||
/// <returns></returns>
|
||||
private float Sqr(float value)
|
||||
{
|
||||
return value * value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Square
|
||||
/// </summary>
|
||||
/// <param name="f"></param>
|
||||
/// <returns></returns>
|
||||
private double Sqr(double value)
|
||||
{
|
||||
return value * value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines poles and zeros of IIR filter
|
||||
/// based on bilinear transform method
|
||||
/// </summary>
|
||||
private void LocatePolesAndZeros()
|
||||
{
|
||||
m_real = new double[m_order + 1];
|
||||
m_imag = new double[m_order + 1];
|
||||
m_z = new double[m_order + 1];
|
||||
double ln10 = Math.Log(10.0);
|
||||
|
||||
// Butterworth, Chebyshev parameters
|
||||
int n = m_order;
|
||||
|
||||
if (m_filterType == FilterType.BP)
|
||||
n = n / 2;
|
||||
|
||||
int ir = n % 2;
|
||||
int n1 = n + ir;
|
||||
int n2 = (3 * n + ir) / 2 - 1;
|
||||
double f1;
|
||||
|
||||
switch (m_filterType)
|
||||
{
|
||||
case FilterType.LP:
|
||||
f1 = m_fp2;
|
||||
break;
|
||||
|
||||
case FilterType.HP:
|
||||
f1 = m_fN - m_fp1;
|
||||
break;
|
||||
|
||||
case FilterType.BP:
|
||||
f1 = m_fp2 - m_fp1;
|
||||
break;
|
||||
|
||||
default:
|
||||
f1 = 0.0;
|
||||
break;
|
||||
}
|
||||
|
||||
double tanw1 = Math.Tan(0.5 * Math.PI * f1 / m_fN);
|
||||
double tansqw1 = Sqr(tanw1);
|
||||
|
||||
// Real and Imaginary parts of low-pass poles
|
||||
double t, a = 1.0, r = 1.0, i = 1.0;
|
||||
|
||||
for (int k = n1; k <= n2; k++)
|
||||
{
|
||||
t = 0.5 * (2 * k + 1 - ir) * Math.PI / (double)n;
|
||||
|
||||
switch (m_protoType)
|
||||
{
|
||||
case ProtoType.Butterworth:
|
||||
double b3 = 1.0 - 2.0 * tanw1 * Math.Cos(t) + tansqw1;
|
||||
r = (1.0 - tansqw1) / b3;
|
||||
i = 2.0 * tanw1 * Math.Sin(t) / b3;
|
||||
break;
|
||||
|
||||
case ProtoType.Chebyshev:
|
||||
double d = 1.0 - Math.Exp(-0.05 * m_ripple * ln10);
|
||||
double e = 1.0 / Math.Sqrt(1.0 / Sqr(1.0 - d) - 1.0);
|
||||
double x = Math.Pow(Math.Sqrt(e * e + 1.0) + e, 1.0 / (double)n);
|
||||
a = 0.5 * (x - 1.0 / x);
|
||||
double b = 0.5 * (x + 1.0 / x);
|
||||
double c3 = a * tanw1 * Math.Cos(t);
|
||||
double c4 = b * tanw1 * Math.Sin(t);
|
||||
double c5 = Sqr(1.0 - c3) + Sqr(c4);
|
||||
r = 2.0 * (1.0 - c3) / c5 - 1.0;
|
||||
i = 2.0 * c4 / c5;
|
||||
break;
|
||||
}
|
||||
|
||||
int m = 2 * (n2 - k) + 1;
|
||||
m_real[m + ir] = r;
|
||||
m_imag[m + ir] = Math.Abs(i);
|
||||
m_real[m + ir + 1] = r;
|
||||
m_imag[m + ir + 1] = -Math.Abs(i);
|
||||
}
|
||||
|
||||
if (Odd(n))
|
||||
{
|
||||
if (m_protoType == ProtoType.Butterworth)
|
||||
r = (1.0 - tansqw1) / (1.0 + 2.0 * tanw1 + tansqw1);
|
||||
|
||||
if (m_protoType == ProtoType.Chebyshev)
|
||||
r = 2.0 / (1.0 + a * tanw1) - 1.0;
|
||||
|
||||
m_real[1] = r;
|
||||
m_imag[1] = 0.0;
|
||||
}
|
||||
|
||||
switch (m_filterType)
|
||||
{
|
||||
case FilterType.LP:
|
||||
for (int m = 1; m <= n; m++)
|
||||
m_z[m] = -1.0;
|
||||
break;
|
||||
|
||||
case FilterType.HP:
|
||||
// Low-pass to high-pass transformation
|
||||
for (int m = 1; m <= n; m++)
|
||||
{
|
||||
m_real[m] = -m_real[m];
|
||||
m_z[m] = 1.0;
|
||||
}
|
||||
break;
|
||||
|
||||
case FilterType.BP:
|
||||
// Low-pass to bandpass transformation
|
||||
for (int m = 1; m <= n; m++)
|
||||
{
|
||||
m_z[m] = 1.0;
|
||||
m_z[m + n] = -1.0;
|
||||
}
|
||||
|
||||
double f4 = 0.5 * Math.PI * m_fp1 / m_fN;
|
||||
double f5 = 0.5 * Math.PI * m_fp2 / m_fN;
|
||||
double aa = Math.Cos(f4 + f5) / Math.Cos(f5 - f4);
|
||||
double aR, aI, h1, h2, p1R, p2R, p1I, p2I;
|
||||
|
||||
for (int m1 = 0; m1 <= (m_order - 1) / 2; m1++)
|
||||
{
|
||||
int m = 1 + 2 * m1;
|
||||
aR = m_real[m];
|
||||
aI = m_imag[m];
|
||||
|
||||
if (Math.Abs(aI) < 0.0001)
|
||||
{
|
||||
h1 = 0.5 * aa * (1.0 + aR);
|
||||
h2 = Sqr(h1) - aR;
|
||||
if (h2 > 0.0)
|
||||
{
|
||||
p1R = h1 + Math.Sqrt(h2);
|
||||
p2R = h1 - Math.Sqrt(h2);
|
||||
p1I = 0.0;
|
||||
p2I = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
p1R = h1;
|
||||
p2R = h1;
|
||||
p1I = Math.Sqrt(Math.Abs(h2));
|
||||
p2I = -p1I;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double fR = aa * 0.5 * (1.0 + aR);
|
||||
double fI = aa * 0.5 * aI;
|
||||
double gR = Sqr(fR) - Sqr(fI) - aR;
|
||||
double gI = 2 * fR * fI - aI;
|
||||
double sR = Math.Sqrt(0.5 * Math.Abs(gR + Math.Sqrt(Sqr(gR) + Sqr(gI))));
|
||||
double sI = gI / (2.0 * sR);
|
||||
p1R = fR + sR;
|
||||
p1I = fI + sI;
|
||||
p2R = fR - sR;
|
||||
p2I = fI - sI;
|
||||
}
|
||||
|
||||
m_real[m] = p1R;
|
||||
m_real[m + 1] = p2R;
|
||||
m_imag[m] = p1I;
|
||||
m_imag[m + 1] = p2I;
|
||||
}
|
||||
|
||||
if (Odd(n))
|
||||
{
|
||||
m_real[2] = m_real[n + 1];
|
||||
m_imag[2] = m_imag[n + 1];
|
||||
}
|
||||
|
||||
for (int k = n; k >= 1; k--)
|
||||
{
|
||||
int m = 2 * k - 1;
|
||||
m_real[m] = m_real[k];
|
||||
m_real[m + 1] = m_real[k];
|
||||
m_imag[m] = Math.Abs(m_imag[k]);
|
||||
m_imag[m + 1] = -Math.Abs(m_imag[k]);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate all the values
|
||||
/// </summary>
|
||||
public void Design()
|
||||
{
|
||||
if (!this.FilterValid)
|
||||
return;
|
||||
|
||||
m_aCoeff = new double[m_order + 1];
|
||||
m_bCoeff = new double[m_order + 1];
|
||||
m_inHistory = new double[kHistSize];
|
||||
m_outHistory = new double[kHistSize];
|
||||
|
||||
double[] newA = new double[m_order + 1];
|
||||
double[] newB = new double[m_order + 1];
|
||||
|
||||
// Find filter poles and zeros
|
||||
LocatePolesAndZeros();
|
||||
|
||||
// Compute filter coefficients from pole/zero values
|
||||
m_aCoeff[0] = 1.0;
|
||||
m_bCoeff[0] = 1.0;
|
||||
|
||||
for (int i = 1; i <= m_order; i++)
|
||||
{
|
||||
m_aCoeff[i] = 0.0;
|
||||
m_bCoeff[i] = 0.0;
|
||||
}
|
||||
|
||||
int k = 0;
|
||||
int n = m_order;
|
||||
int pairs = n / 2;
|
||||
|
||||
if (Odd(m_order))
|
||||
{
|
||||
// First subfilter is first order
|
||||
m_aCoeff[1] = -m_z[1];
|
||||
m_bCoeff[1] = -m_real[1];
|
||||
k = 1;
|
||||
}
|
||||
|
||||
for (int p = 1; p <= pairs; p++)
|
||||
{
|
||||
int m = 2 * p - 1 + k;
|
||||
double alpha1 = -(m_z[m] + m_z[m + 1]);
|
||||
double alpha2 = m_z[m] * m_z[m + 1];
|
||||
double beta1 = -2.0 * m_real[m];
|
||||
double beta2 = Sqr(m_real[m]) + Sqr(m_imag[m]);
|
||||
|
||||
newA[1] = m_aCoeff[1] + alpha1 * m_aCoeff[0];
|
||||
newB[1] = m_bCoeff[1] + beta1 * m_bCoeff[0];
|
||||
|
||||
for (int i = 2; i <= n; i++)
|
||||
{
|
||||
newA[i] = m_aCoeff[i] + alpha1 * m_aCoeff[i - 1] + alpha2 * m_aCoeff[i - 2];
|
||||
newB[i] = m_bCoeff[i] + beta1 * m_bCoeff[i - 1] + beta2 * m_bCoeff[i - 2];
|
||||
}
|
||||
|
||||
for (int i = 1; i <= n; i++)
|
||||
{
|
||||
m_aCoeff[i] = newA[i];
|
||||
m_bCoeff[i] = newB[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the filter is normalized
|
||||
FilterGain(1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the history buffers
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
if (m_inHistory != null)
|
||||
m_inHistory.Clear();
|
||||
|
||||
if (m_outHistory != null)
|
||||
m_outHistory.Clear();
|
||||
|
||||
m_histIdx = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the filter, and fill the appropriate history buffers with the specified value
|
||||
/// </summary>
|
||||
/// <param name="historyValue"></param>
|
||||
public void Reset(double startValue)
|
||||
{
|
||||
m_histIdx = 0;
|
||||
|
||||
if (m_inHistory == null || m_outHistory == null)
|
||||
return;
|
||||
|
||||
m_inHistory.Fill(startValue);
|
||||
|
||||
if (m_inHistory != null)
|
||||
{
|
||||
switch (m_filterType)
|
||||
{
|
||||
case FilterType.LP:
|
||||
m_outHistory.Fill(startValue);
|
||||
break;
|
||||
|
||||
default:
|
||||
m_outHistory.Clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply the filter to the buffer
|
||||
/// </summary>
|
||||
/// <param name="bufIn"></param>
|
||||
public void FilterBuffer(float[] srcBuf, long srcPos, float[] dstBuf, long dstPos, long nLen)
|
||||
{
|
||||
const double kDenormal = 0.000000000000001;
|
||||
double denormal = m_invertDenormal ? -kDenormal : kDenormal;
|
||||
m_invertDenormal = !m_invertDenormal;
|
||||
|
||||
for (int sampleIdx = 0; sampleIdx < nLen; sampleIdx++)
|
||||
{
|
||||
double sum = 0.0f;
|
||||
|
||||
m_inHistory[m_histIdx] = srcBuf[srcPos + sampleIdx] + denormal;
|
||||
|
||||
for (int idx = 0; idx < m_aCoeff.Length; idx++)
|
||||
sum += m_aCoeff[idx] * m_inHistory[(m_histIdx - idx) & kHistMask];
|
||||
|
||||
for (int idx = 1; idx < m_bCoeff.Length; idx++)
|
||||
sum -= m_bCoeff[idx] * m_outHistory[(m_histIdx - idx) & kHistMask];
|
||||
|
||||
m_outHistory[m_histIdx] = sum;
|
||||
m_histIdx = (m_histIdx + 1) & kHistMask;
|
||||
dstBuf[dstPos + sampleIdx] = (float)sum;
|
||||
}
|
||||
}
|
||||
|
||||
public float FilterSample(float inVal)
|
||||
{
|
||||
double sum = 0.0f;
|
||||
|
||||
m_inHistory[m_histIdx] = inVal;
|
||||
|
||||
for (int idx = 0; idx < m_aCoeff.Length; idx++)
|
||||
sum += m_aCoeff[idx] * m_inHistory[(m_histIdx - idx) & kHistMask];
|
||||
|
||||
for (int idx = 1; idx < m_bCoeff.Length; idx++)
|
||||
sum -= m_bCoeff[idx] * m_outHistory[(m_histIdx - idx) & kHistMask];
|
||||
|
||||
m_outHistory[m_histIdx] = sum;
|
||||
m_histIdx = (m_histIdx + 1) & kHistMask;
|
||||
|
||||
return (float)sum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the gain at the specified number of frequency points
|
||||
/// </summary>
|
||||
/// <param name="freqPoints"></param>
|
||||
/// <returns></returns>
|
||||
public float[] FilterGain(int freqPoints)
|
||||
{
|
||||
// Filter gain at uniform frequency intervals
|
||||
float[] g = new float[freqPoints];
|
||||
double theta, s, c, sac, sas, sbc, sbs;
|
||||
float gMax = -100.0f;
|
||||
float sc = 10.0f / (float)Math.Log(10.0f);
|
||||
double t = Math.PI / (freqPoints - 1);
|
||||
|
||||
for (int i = 0; i < freqPoints; i++)
|
||||
{
|
||||
theta = i * t;
|
||||
|
||||
if (i == 0)
|
||||
theta = Math.PI * 0.0001;
|
||||
|
||||
if (i == freqPoints - 1)
|
||||
theta = Math.PI * 0.9999;
|
||||
|
||||
sac = 0.0f;
|
||||
sas = 0.0f;
|
||||
sbc = 0.0f;
|
||||
sbs = 0.0f;
|
||||
|
||||
for (int k = 0; k <= m_order; k++)
|
||||
{
|
||||
c = Math.Cos(k * theta);
|
||||
s = Math.Sin(k * theta);
|
||||
sac += c * m_aCoeff[k];
|
||||
sas += s * m_aCoeff[k];
|
||||
sbc += c * m_bCoeff[k];
|
||||
sbs += s * m_bCoeff[k];
|
||||
}
|
||||
|
||||
g[i] = sc * (float)Math.Log((Sqr(sac) + Sqr(sas)) / (Sqr(sbc) + Sqr(sbs)));
|
||||
gMax = Math.Max(gMax, g[i]);
|
||||
}
|
||||
|
||||
// Normalize to 0 dB maximum gain
|
||||
for (int i = 0; i < freqPoints; i++)
|
||||
g[i] -= gMax;
|
||||
|
||||
// Normalize numerator (a) coefficients
|
||||
float normFactor = (float)Math.Pow(10.0, -0.05 * gMax);
|
||||
|
||||
for (int i = 0; i <= m_order; i++)
|
||||
m_aCoeff[i] *= normFactor;
|
||||
|
||||
return g;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
using System;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker.Pitch
|
||||
{
|
||||
/// <summary>
|
||||
/// Pitch related DSP
|
||||
/// </summary>
|
||||
public class PitchDsp
|
||||
{
|
||||
public static readonly double InverseLog2 = 1.0 / Math.Log10(2.0);
|
||||
|
||||
private const int kCourseOctaveSteps = 96;
|
||||
private const int kScanHiSize = 31;
|
||||
private const float kScanHiFreqStep = 1.005f;
|
||||
private const int kMinMidiNote = 21; // A0
|
||||
private const int kMaxMidiNote = 108; // C8
|
||||
|
||||
private float m_minPitch;
|
||||
private float m_maxPitch;
|
||||
private int m_minNote;
|
||||
private int m_maxNote;
|
||||
private int m_blockLen14; // 1/4 block len
|
||||
private int m_blockLen24; // 2/4 block len
|
||||
private int m_blockLen34; // 3/4 block len
|
||||
private int m_blockLen44; // 4/4 block len
|
||||
private double m_sampleRate;
|
||||
|
||||
private int m_numCourseSteps;
|
||||
private float[] m_pCourseFreqOffset;
|
||||
private float[] m_pCourseFreq;
|
||||
private float[] m_scanHiOffset = new float[kScanHiSize];
|
||||
private float[] m_peakBuf = new float[kScanHiSize];
|
||||
private int m_prevPitchIdx;
|
||||
private float[] m_detectCurve;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="fSampleRate"></param>
|
||||
/// <param name="minFreq"></param>
|
||||
/// <param name="maxFreq"></param>
|
||||
/// <param name="fFreqStep"></param>
|
||||
public PitchDsp(double sampleRate, float minPitch, float maxPitch)
|
||||
{
|
||||
m_sampleRate = sampleRate;
|
||||
m_minPitch = minPitch;
|
||||
m_maxPitch = maxPitch;
|
||||
|
||||
m_minNote = (int)(PitchToMidiNote(m_minPitch) + 0.5f) + 2;
|
||||
m_maxNote = (int)(PitchToMidiNote(m_maxPitch) + 0.5f) - 2;
|
||||
|
||||
m_blockLen44 = (int)(m_sampleRate / m_minPitch + 0.5f);
|
||||
m_blockLen34 = (m_blockLen44 * 3) / 4;
|
||||
m_blockLen24 = m_blockLen44 / 2;
|
||||
m_blockLen14 = m_blockLen44 / 4;
|
||||
|
||||
m_numCourseSteps = (int)((Math.Log((double)m_maxPitch / (double)m_minPitch) / Math.Log(2.0)) * kCourseOctaveSteps + 0.5) + 3;
|
||||
|
||||
m_pCourseFreqOffset = new float[m_numCourseSteps + 10000];
|
||||
m_pCourseFreq = new float[m_numCourseSteps + 10000];
|
||||
|
||||
m_detectCurve = new float[m_numCourseSteps];
|
||||
|
||||
double freqStep = 1.0 / Math.Pow(2.0, 1.0 / kCourseOctaveSteps);
|
||||
double curFreq = m_maxPitch / freqStep;
|
||||
|
||||
// frequency is stored from high to low
|
||||
for (int idx = 0; idx < m_numCourseSteps; idx++)
|
||||
{
|
||||
m_pCourseFreq[idx] = (float)curFreq;
|
||||
m_pCourseFreqOffset[idx] = (float)(m_sampleRate / curFreq);
|
||||
curFreq *= freqStep;
|
||||
}
|
||||
|
||||
for (int idx = 0; idx < kScanHiSize; idx++)
|
||||
m_scanHiOffset[idx] = (float)Math.Pow(kScanHiFreqStep, (kScanHiSize / 2) - idx);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the max detected pitch
|
||||
/// </summary>
|
||||
public float MaxPitch
|
||||
{
|
||||
get { return m_maxPitch; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the min detected pitch
|
||||
/// </summary>
|
||||
public float MinPitch
|
||||
{
|
||||
get { return m_minPitch; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the max note
|
||||
/// </summary>
|
||||
public int MaxNote
|
||||
{
|
||||
get { return m_maxNote; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the min note
|
||||
/// </summary>
|
||||
public int MinNote
|
||||
{
|
||||
get { return m_minNote; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect the pitch
|
||||
/// </summary>
|
||||
public float DetectPitch(float[] samplesLo, float[] samplesHi)
|
||||
{
|
||||
return DetectPitchLo(samplesLo, samplesHi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Low resolution pitch detection
|
||||
/// </summary>
|
||||
/// <param name="dataIdx"></param>
|
||||
/// <param name="begFreqIdx"></param>
|
||||
/// <param name="endFreqIdx"></param>
|
||||
/// <param name="blockLen"></param>
|
||||
/// <param name="stepSize"></param>
|
||||
/// <returns></returns>
|
||||
private float DetectPitchLo(float[] samplesLo, float[] samplesHi)
|
||||
{
|
||||
m_detectCurve.Clear();
|
||||
|
||||
const int skipSize = 8;
|
||||
const int peakScanSize = 23;
|
||||
const int peakScanSizeHalf = peakScanSize / 2;
|
||||
|
||||
float peakThresh1 = 200.0f;
|
||||
float peakThresh2 = 600.0f;
|
||||
bool bufferSwitched = false;
|
||||
|
||||
for (int idx = 0; idx < m_numCourseSteps; idx += skipSize)
|
||||
{
|
||||
int blockLen = Math.Min(m_blockLen44, (int)m_pCourseFreqOffset[idx] * 2);
|
||||
float[] curSamples = null;
|
||||
|
||||
// 258 is at 250 Hz, which is the switchover frequency for the two filters
|
||||
bool loBuffer = idx >= 258;
|
||||
|
||||
if (loBuffer)
|
||||
{
|
||||
if (!bufferSwitched)
|
||||
{
|
||||
m_detectCurve.Clear(258 - peakScanSizeHalf, 258 + peakScanSizeHalf);
|
||||
bufferSwitched = true;
|
||||
}
|
||||
|
||||
curSamples = samplesLo;
|
||||
}
|
||||
else
|
||||
curSamples = samplesHi;
|
||||
|
||||
int stepSizeLoRes = blockLen / 10;
|
||||
int stepSizeHiRes = Math.Max(1, Math.Min(5, idx * 5 / m_numCourseSteps));
|
||||
|
||||
float fValue = RatioAbsDiffLinear(curSamples, idx, blockLen, stepSizeLoRes, false);
|
||||
|
||||
if (fValue > peakThresh1)
|
||||
{
|
||||
// Do a closer search for the peak
|
||||
int peakIdx = -1;
|
||||
float peakVal = 0.0f;
|
||||
float prevVal = 0.0f;
|
||||
int dir = 4; // start going forward
|
||||
int curPos = idx; // start at center of the scan range
|
||||
int begSearch = Math.Max(idx - peakScanSizeHalf, 0);
|
||||
int endSearch = Math.Min(idx + peakScanSizeHalf, m_numCourseSteps - 1);
|
||||
|
||||
while (curPos >= begSearch && curPos < endSearch)
|
||||
{
|
||||
float curVal = RatioAbsDiffLinear(curSamples, curPos, blockLen, stepSizeHiRes, true);
|
||||
|
||||
if (peakVal < curVal)
|
||||
{
|
||||
peakVal = curVal;
|
||||
peakIdx = curPos;
|
||||
}
|
||||
|
||||
if (prevVal > curVal)
|
||||
{
|
||||
dir = -dir >> 1;
|
||||
|
||||
if (dir == 0)
|
||||
{
|
||||
if (peakVal > peakThresh2 && peakIdx >= 6 && peakIdx <= m_numCourseSteps - 7)
|
||||
{
|
||||
float fValL = RatioAbsDiffLinear(curSamples, peakIdx - 5, blockLen, stepSizeHiRes, true);
|
||||
float fValR = RatioAbsDiffLinear(curSamples, peakIdx + 5, blockLen, stepSizeHiRes, true);
|
||||
float fPointy = peakVal / (fValL + fValR) * 2.0f;
|
||||
|
||||
float minPointy = (m_prevPitchIdx > 0 && Math.Abs(m_prevPitchIdx - peakIdx) < 10) ? 1.2f : 1.5f;
|
||||
|
||||
if (fPointy > minPointy)
|
||||
{
|
||||
float pitchHi = DetectPitchHi(curSamples, peakIdx);
|
||||
|
||||
if (pitchHi > 1.0f)
|
||||
{
|
||||
m_prevPitchIdx = peakIdx;
|
||||
return pitchHi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
prevVal = curVal;
|
||||
curPos += dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_prevPitchIdx = 0;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// High resolution pitch detection
|
||||
/// </summary>
|
||||
/// <param name="dataIdx"></param>
|
||||
/// <param name="lowFreqIdx"></param>
|
||||
/// <returns></returns>
|
||||
private float DetectPitchHi(float[] samples, int lowFreqIdx)
|
||||
{
|
||||
int peakIdx = -1;
|
||||
float prevVal = 0.0f;
|
||||
int dir = 4; // start going forward
|
||||
int curPos = kScanHiSize >> 1; // start at center of the scan range
|
||||
|
||||
m_peakBuf.Clear();
|
||||
|
||||
float offset = m_pCourseFreqOffset[lowFreqIdx];
|
||||
|
||||
while (curPos >= 0 && curPos < kScanHiSize)
|
||||
{
|
||||
if (m_peakBuf[curPos] == 0.0f)
|
||||
m_peakBuf[curPos] = SumAbsDiffHermite(samples, offset * m_scanHiOffset[curPos], m_blockLen44, 1);
|
||||
|
||||
if (peakIdx < 0 || m_peakBuf[peakIdx] < m_peakBuf[curPos])
|
||||
peakIdx = curPos;
|
||||
|
||||
if (prevVal > m_peakBuf[curPos])
|
||||
{
|
||||
dir = -dir >> 1;
|
||||
|
||||
if (dir == 0)
|
||||
{
|
||||
// found the peak
|
||||
float minVal = Math.Min(m_peakBuf[peakIdx - 1], m_peakBuf[peakIdx + 1]);
|
||||
|
||||
minVal -= minVal * (1.0f / 32.0f);
|
||||
|
||||
var y1 = (float)Math.Log10(m_peakBuf[peakIdx - 1] - minVal);
|
||||
var y2 = (float)Math.Log10(m_peakBuf[peakIdx] - minVal);
|
||||
var y3 = (float)Math.Log10(m_peakBuf[peakIdx + 1] - minVal);
|
||||
|
||||
float fIdx = (float)peakIdx + (y3 - y1) / (2.0f * (2.0f * y2 - y1 - y3));
|
||||
|
||||
return (float)Math.Pow(kScanHiFreqStep, fIdx - (kScanHiSize / 2)) * m_pCourseFreq[lowFreqIdx];
|
||||
}
|
||||
}
|
||||
|
||||
prevVal = m_peakBuf[curPos];
|
||||
curPos += dir;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a sine wave with the specified frequency, amplitude and starting angle.
|
||||
/// Returns the updated angle.
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="numSamples"></param>
|
||||
/// <param name="freq"></param>
|
||||
/// <param name="amplitude"></param>
|
||||
/// <param name="startAngle"></param>
|
||||
/// <returns></returns>
|
||||
public static double CreateSineWave(float[] buffer, int numSamples, float sampleRate,
|
||||
float freq, float amplitude, double startAngle)
|
||||
{
|
||||
double angleStep = freq / sampleRate * Math.PI * 2.0;
|
||||
double curAngle = startAngle;
|
||||
|
||||
for (int idx = 0; idx < numSamples; idx++)
|
||||
{
|
||||
buffer[idx] = (float)Math.Sin(curAngle) * amplitude;
|
||||
|
||||
curAngle += angleStep;
|
||||
|
||||
while (curAngle > Math.PI)
|
||||
curAngle -= Math.PI * 2.0;
|
||||
}
|
||||
|
||||
return curAngle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the level is above the specified value
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="startIdx"></param>
|
||||
/// <param name="len"></param>
|
||||
/// <param name="level"></param>
|
||||
/// <returns></returns>
|
||||
public bool LevelIsAbove(float[] buffer, int len, float level)
|
||||
{
|
||||
if (buffer == null || buffer.Length == 0)
|
||||
return false;
|
||||
|
||||
int endIdx = Math.Min(buffer.Length, len);
|
||||
|
||||
for (int idx = 0; idx < endIdx; idx++)
|
||||
{
|
||||
if (Math.Abs(buffer[idx]) >= level)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy the data from the source to the destination buffer
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="destination"></param>
|
||||
/// <param name="startPos"></param>
|
||||
/// <param name="length"></param>
|
||||
public static void CopyBuffer<T>(T[] source, int srcStart, T[] destination, int dstStart, int length)
|
||||
{
|
||||
if (length < 0)
|
||||
throw new ArgumentOutOfRangeException("length");
|
||||
|
||||
if (source == null || source.Length < srcStart + length)
|
||||
throw new Exception("Source buffer is null or not large enough");
|
||||
|
||||
if (destination == null || destination.Length < dstStart + length)
|
||||
throw new Exception("Destination buffer is null or not large enough");
|
||||
|
||||
int srcIdx = srcStart;
|
||||
int dstIdx = dstStart;
|
||||
|
||||
for (int idx = 0; idx < length; idx++)
|
||||
destination[dstIdx++] = source[srcIdx++];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// // 4-point, 3rd-order Hermite (x-form)
|
||||
/// </summary>
|
||||
private float InterpolateHermite(float fY0, float fY1, float fY2, float fY3, float frac)
|
||||
{
|
||||
float c1 = 0.5f * (fY2 - fY0);
|
||||
float c3 = 1.5f * (fY1 - fY2) + 0.5f * (fY3 - fY0);
|
||||
float c2 = fY0 - fY1 + c1 - c3;
|
||||
|
||||
return ((c3 * frac + c2) * frac + c1) * frac + fY1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linear interpolation
|
||||
/// nFrac is based on 1.0 = 256
|
||||
/// </summary>
|
||||
private float InterpolateLinear(float y0, float y1, float frac)
|
||||
{
|
||||
return y0 * (1.0f - frac) + y1 * frac;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Medium Low res SumAbsDiff
|
||||
/// </summary>
|
||||
private float RatioAbsDiffLinear(float[] samples, int freqIdx, int blockLen, int stepSize, bool hiRes)
|
||||
{
|
||||
if (hiRes && m_detectCurve[freqIdx] > 0.0f)
|
||||
return m_detectCurve[freqIdx];
|
||||
|
||||
var offsetInt = (int)m_pCourseFreqOffset[freqIdx];
|
||||
float offsetFrac = m_pCourseFreqOffset[freqIdx] - offsetInt;
|
||||
float rect = 0.0f;
|
||||
float absDiff = 0.01f; // prevent divide by zero
|
||||
int count = 0;
|
||||
float interp;
|
||||
float sample;
|
||||
|
||||
// Do a scan using linear interpolation and the specified step size
|
||||
for (int idx = 0; idx < blockLen; idx += stepSize, count++)
|
||||
{
|
||||
sample = samples[idx];
|
||||
interp = InterpolateLinear(samples[offsetInt + idx], samples[offsetInt + idx + 1], offsetFrac);
|
||||
absDiff += Math.Abs(sample - interp);
|
||||
rect += Math.Abs(sample) + Math.Abs(interp);
|
||||
}
|
||||
|
||||
float finalVal = rect / absDiff * 100.0f;
|
||||
|
||||
if (hiRes)
|
||||
m_detectCurve[freqIdx] = finalVal;
|
||||
|
||||
return finalVal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Medium High res SumAbsDiff
|
||||
/// </summary>
|
||||
private float SumAbsDiffHermite(float[] samples, float fOffset, int blockLen, int stepSize)
|
||||
{
|
||||
var offsetInt = (int)fOffset;
|
||||
float offsetFrac = fOffset - offsetInt;
|
||||
float value = 0.001f; // prevent divide by zero
|
||||
int count = 0;
|
||||
|
||||
// do a scan using linear interpolation and the specified step size
|
||||
for (int idx = 0; idx < blockLen; idx += stepSize, count++)
|
||||
{
|
||||
int offsetIdx = offsetInt + idx;
|
||||
|
||||
value += Math.Abs(samples[idx] - InterpolateHermite(samples[offsetIdx - 1],
|
||||
samples[offsetIdx],
|
||||
samples[offsetIdx + 1],
|
||||
samples[offsetIdx + 2],
|
||||
offsetFrac));
|
||||
}
|
||||
|
||||
return count / value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the MIDI note and cents of the pitch
|
||||
/// </summary>
|
||||
/// <param name="pitch"></param>
|
||||
/// <param name="note"></param>
|
||||
/// <param name="cents"></param>
|
||||
/// <returns></returns>
|
||||
public static bool PitchToMidiNote(float pitch, out int note, out int cents)
|
||||
{
|
||||
if (pitch < 20.0f)
|
||||
{
|
||||
note = 0;
|
||||
cents = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
float fNote = (float)((12.0 * Math.Log10(pitch / 55.0) * InverseLog2)) + 33.0f;
|
||||
note = (int)(fNote + 0.5f);
|
||||
cents = (int)((note - fNote) * 100);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the pitch from the MIDI note
|
||||
/// </summary>
|
||||
/// <param name="pitch"></param>
|
||||
/// <returns></returns>
|
||||
public static float PitchToMidiNote(float pitch)
|
||||
{
|
||||
if (pitch < 20.0f)
|
||||
return 0.0f;
|
||||
|
||||
return (float)(12.0 * Math.Log10(pitch / 55.0) * InverseLog2) + 33.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the pitch from the MIDI note
|
||||
/// </summary>
|
||||
/// <param name="note"></param>
|
||||
/// <returns></returns>
|
||||
public float MidiNoteToPitch(float note)
|
||||
{
|
||||
if (note < 33.0f)
|
||||
return 0.0f;
|
||||
|
||||
float pitch = (float)Math.Pow(10.0, (note - 33.0f) / InverseLog2 / 12.0f) * 55.0f;
|
||||
return pitch <= m_maxPitch ? pitch : 0.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format a midi note to text
|
||||
/// </summary>
|
||||
/// <param name="note"></param>
|
||||
/// <param name="sharps"></param>
|
||||
/// <param name="showOctave"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetNoteName(int note, bool sharps, bool showOctave)
|
||||
{
|
||||
if (note < kMinMidiNote || note > kMaxMidiNote)
|
||||
return null;
|
||||
|
||||
note -= kMinMidiNote;
|
||||
|
||||
int octave = (note + 9) / 12;
|
||||
note = note % 12;
|
||||
string noteText = null;
|
||||
|
||||
switch (note)
|
||||
{
|
||||
case 0:
|
||||
noteText = "A";
|
||||
break;
|
||||
|
||||
case 1:
|
||||
noteText = sharps ? "A#" : "Bb";
|
||||
break;
|
||||
|
||||
case 2:
|
||||
noteText = "B";
|
||||
break;
|
||||
|
||||
case 3:
|
||||
noteText = "C";
|
||||
break;
|
||||
|
||||
case 4:
|
||||
noteText = sharps ? "C#" : "Db";
|
||||
break;
|
||||
|
||||
case 5:
|
||||
noteText = "D";
|
||||
break;
|
||||
|
||||
case 6:
|
||||
noteText = sharps ? "D#" : "Eb";
|
||||
break;
|
||||
|
||||
case 7:
|
||||
noteText = "E";
|
||||
break;
|
||||
|
||||
case 8:
|
||||
noteText = "F";
|
||||
break;
|
||||
|
||||
case 9:
|
||||
noteText = sharps ? "F#" : "Gb";
|
||||
break;
|
||||
|
||||
case 10:
|
||||
noteText = "G";
|
||||
break;
|
||||
|
||||
case 11:
|
||||
noteText = sharps ? "G#" : "Ab";
|
||||
break;
|
||||
}
|
||||
|
||||
if (showOctave)
|
||||
noteText += " " + octave.ToString();
|
||||
|
||||
return noteText;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PitchTracker.Pitch
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracks pitch
|
||||
/// </summary>
|
||||
// ReSharper disable InconsistentNaming
|
||||
public class PitchTracker
|
||||
{
|
||||
private const int kOctaveSteps = 96;
|
||||
private const int kStepOverlap = 4;
|
||||
private const float kMinFreq = 50.0f; // A1, Midi note 33, 55.0Hz
|
||||
private const float kMaxFreq = 1800.0f; // A#6. Midi note 92
|
||||
private const int kStartCircular = 40; // how far into the sample buffer do we start checking (allow for filter settling)
|
||||
private const float kDetectOverlapSec = 0.005f;
|
||||
private const float kMaxOctaveSecRate = 10.0f;
|
||||
|
||||
private const float kAvgOffset = 0.005f; // time offset between pitch averaging values
|
||||
private const int kAvgCount = 1; // number of average pitch samples to take
|
||||
private const float kCircularBufSaveTime = 1.0f; // Amount of samples to store in the history buffer
|
||||
|
||||
private PitchDsp m_dsp;
|
||||
private CircularBuffer<float> m_circularBufferLo;
|
||||
private CircularBuffer<float> m_circularBufferHi;
|
||||
private double m_sampleRate;
|
||||
private float m_detectLevelThreshold = 0.01f; // -40dB
|
||||
private int m_pitchRecordsPerSecond = 50; // default is 50, or one record every 20ms
|
||||
|
||||
private float[] m_pitchBufLo;
|
||||
private float[] m_pitchBufHi;
|
||||
private int m_pitchBufSize;
|
||||
private int m_samplesPerPitchBlock;
|
||||
private int m_curPitchIndex;
|
||||
private long m_curPitchSamplePos;
|
||||
|
||||
private int m_detectOverlapSamples;
|
||||
private float m_maxOverlapDiff;
|
||||
|
||||
private bool m_recordPitchRecords;
|
||||
private int m_pitchRecordHistorySize;
|
||||
private List<PitchRecord> m_pitchRecords = new List<PitchRecord>();
|
||||
private PitchRecord m_curPitchRecord = new PitchRecord();
|
||||
|
||||
private IIRFilter m_iirFilterLoLo;
|
||||
private IIRFilter m_iirFilterLoHi;
|
||||
private IIRFilter m_iirFilterHiLo;
|
||||
private IIRFilter m_iirFilterHiHi;
|
||||
|
||||
public float LastMaxVol = 0;
|
||||
|
||||
public delegate void PitchDetectedHandler(PitchTracker sender, PitchRecord pitchRecord);
|
||||
|
||||
public event PitchDetectedHandler PitchDetected;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public PitchTracker() {}
|
||||
|
||||
/// <summary>
|
||||
/// Set the sample rate
|
||||
/// </summary>
|
||||
public double SampleRate
|
||||
{
|
||||
set
|
||||
{
|
||||
if (m_sampleRate == value)
|
||||
return;
|
||||
|
||||
m_sampleRate = value;
|
||||
Setup();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the detect level threshold, The value must be between 0.0001f and 1.0f (-80 dB to 0 dB)
|
||||
/// </summary>
|
||||
public float DetectLevelThreshold
|
||||
{
|
||||
set
|
||||
{
|
||||
float newValue = Math.Max(0.0001f, Math.Min(1.0f, value));
|
||||
|
||||
if (m_detectLevelThreshold == newValue)
|
||||
return;
|
||||
|
||||
m_detectLevelThreshold = newValue;
|
||||
//Setup();
|
||||
}
|
||||
get { return m_detectLevelThreshold; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the samples per pitch block
|
||||
/// </summary>
|
||||
public int SamplesPerPitchBlock
|
||||
{
|
||||
get { return m_samplesPerPitchBlock; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the number of pitch records per second (default is 50, or one record every 20ms)
|
||||
/// </summary>
|
||||
public int PitchRecordsPerSecond
|
||||
{
|
||||
get { return m_pitchRecordsPerSecond; }
|
||||
set
|
||||
{
|
||||
m_pitchRecordsPerSecond = Math.Max(1, Math.Min(100, value));
|
||||
Setup();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set whether pitch records should be recorded into a history buffer
|
||||
/// </summary>
|
||||
public bool RecordPitchRecords
|
||||
{
|
||||
get { return m_recordPitchRecords; }
|
||||
set
|
||||
{
|
||||
if (m_recordPitchRecords == value)
|
||||
return;
|
||||
|
||||
m_recordPitchRecords = value;
|
||||
|
||||
if (!m_recordPitchRecords)
|
||||
m_pitchRecords = new List<PitchRecord>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the max number of pitch records to keep. A value of 0 means no limit.
|
||||
/// Don't leave this at 0 when RecordPitchRecords is true and this is used in a realtime
|
||||
/// application since the buffer will grow indefinately!
|
||||
/// </summary>
|
||||
public int PitchRecordHistorySize
|
||||
{
|
||||
get { return m_pitchRecordHistorySize; }
|
||||
set
|
||||
{
|
||||
m_pitchRecordHistorySize = value;
|
||||
|
||||
m_pitchRecords.Capacity = m_pitchRecordHistorySize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current pitch records
|
||||
/// </summary>
|
||||
public IList PitchRecords
|
||||
{
|
||||
get { return m_pitchRecords.AsReadOnly(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the latest pitch record
|
||||
/// </summary>
|
||||
public PitchRecord CurrentPitchRecord
|
||||
{
|
||||
get { return m_curPitchRecord; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current pitch position
|
||||
/// </summary>
|
||||
public long CurrentPitchSamplePosition
|
||||
{
|
||||
get { return m_curPitchSamplePos; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the minimum frequency that can be detected
|
||||
/// </summary>
|
||||
public static float MinDetectFrequency
|
||||
{
|
||||
get { return kMinFreq; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the maximum frequency that can be detected
|
||||
/// </summary>
|
||||
public static float MaxDetectFrequency
|
||||
{
|
||||
get { return kMaxFreq; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the frequency step
|
||||
/// </summary>
|
||||
public static double FrequencyStep
|
||||
{
|
||||
get { return Math.Pow(2.0, 1.0 / kOctaveSteps); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of samples that the detected pitch is offset from the input buffer.
|
||||
/// This is just an estimate to sync up the samples and detected pitch
|
||||
/// </summary>
|
||||
public int DetectSampleOffset
|
||||
{
|
||||
get { return (m_pitchBufSize + m_detectOverlapSamples) / 2; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the pitch tracker. Call this when the sample position is
|
||||
/// not consecutive from the previous position
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
m_curPitchIndex = 0;
|
||||
m_curPitchSamplePos = 0;
|
||||
m_pitchRecords.Clear();
|
||||
m_iirFilterLoLo.Reset();
|
||||
m_iirFilterLoHi.Reset();
|
||||
m_iirFilterHiLo.Reset();
|
||||
m_iirFilterHiHi.Reset();
|
||||
m_circularBufferLo.Reset();
|
||||
m_circularBufferLo.Clear();
|
||||
m_circularBufferHi.Reset();
|
||||
m_circularBufferHi.Clear();
|
||||
m_pitchBufLo.Clear();
|
||||
m_pitchBufHi.Clear();
|
||||
|
||||
m_circularBufferLo.StartPosition = -m_detectOverlapSamples;
|
||||
m_circularBufferLo.Available = m_detectOverlapSamples;
|
||||
m_circularBufferHi.StartPosition = -m_detectOverlapSamples;
|
||||
m_circularBufferHi.Available = m_detectOverlapSamples;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the passed in buffer of data. During this call, the PitchDetected event will
|
||||
/// be fired zero or more times, depending how many pitch records will fit in the new
|
||||
/// and previously cached buffer.
|
||||
///
|
||||
/// This means that there is no size restriction on the buffer that is passed into ProcessBuffer.
|
||||
/// For instance, ProcessBuffer can be called with one very large buffer that contains all of the
|
||||
/// audio to be processed (many PitchDetected events will be fired), or just a small buffer at
|
||||
/// a time which is more typical for realtime applications. In the latter case, the PitchDetected
|
||||
/// event might not be fired at all since additional calls must first be made to accumulate enough
|
||||
/// data do another pitch detect operation.
|
||||
/// </summary>
|
||||
/// <param name="inBuffer">Input buffer. Samples must be in the range -1.0 to 1.0</param>
|
||||
/// <param name="maxVolume"></param>
|
||||
/// <param name="sampleCount">Number of samples to process. Zero means all samples in the buffer</param>
|
||||
public void ProcessBuffer(float[] inBuffer, int sampleCount = 0)
|
||||
{
|
||||
if (inBuffer == null)
|
||||
throw new ArgumentNullException("inBuffer", "Input buffer cannot be null");
|
||||
|
||||
int samplesProcessed = 0;
|
||||
int srcLength = sampleCount == 0 ? inBuffer.Length : Math.Min(sampleCount, inBuffer.Length);
|
||||
|
||||
float maxVolume = LastMaxVol * 0.85f;
|
||||
|
||||
while (samplesProcessed < srcLength)
|
||||
{
|
||||
int frameCount = Math.Min(srcLength - samplesProcessed, m_pitchBufSize + m_detectOverlapSamples);
|
||||
|
||||
m_iirFilterLoLo.FilterBuffer(inBuffer, samplesProcessed, m_pitchBufLo, 0, frameCount);
|
||||
m_iirFilterLoHi.FilterBuffer(m_pitchBufLo, 0, m_pitchBufLo, 0, frameCount);
|
||||
|
||||
m_iirFilterHiLo.FilterBuffer(inBuffer, samplesProcessed, m_pitchBufHi, 0, frameCount);
|
||||
m_iirFilterHiHi.FilterBuffer(m_pitchBufHi, 0, m_pitchBufHi, 0, frameCount);
|
||||
|
||||
m_circularBufferLo.WriteBuffer(m_pitchBufLo, frameCount);
|
||||
m_circularBufferHi.WriteBuffer(m_pitchBufHi, frameCount);
|
||||
|
||||
// Loop while there is enough samples in the circular buffer
|
||||
while (m_circularBufferLo.ReadBuffer(m_pitchBufLo, m_curPitchSamplePos, m_pitchBufSize + m_detectOverlapSamples))
|
||||
{
|
||||
float pitch1;
|
||||
float pitch2;
|
||||
float detectedPitch = 0.0f;
|
||||
|
||||
m_circularBufferHi.ReadBuffer(m_pitchBufHi, m_curPitchSamplePos, m_pitchBufSize + m_detectOverlapSamples);
|
||||
|
||||
maxVolume = 0f;
|
||||
for (int i = 0; i < (m_pitchBufSize + m_detectOverlapSamples) / 2; i++)
|
||||
{
|
||||
if (maxVolume < m_pitchBufHi[i])
|
||||
maxVolume = m_pitchBufHi[i];
|
||||
if (maxVolume < m_pitchBufLo[i])
|
||||
maxVolume = m_pitchBufLo[i];
|
||||
}
|
||||
|
||||
pitch1 = (maxVolume >= m_detectLevelThreshold) ? m_dsp.DetectPitch(m_pitchBufLo, m_pitchBufHi) : 0f;
|
||||
|
||||
if (pitch1 > 0.0f)
|
||||
{
|
||||
// Shift the buffers left by the overlapping amount
|
||||
m_pitchBufLo.Copy(m_pitchBufLo, m_detectOverlapSamples - 1, 0, m_pitchBufSize);
|
||||
m_pitchBufHi.Copy(m_pitchBufHi, m_detectOverlapSamples - 1, 0, m_pitchBufSize);
|
||||
|
||||
pitch2 = m_dsp.DetectPitch(m_pitchBufLo, m_pitchBufHi);
|
||||
|
||||
if (pitch2 > 0.0f)
|
||||
{
|
||||
float fDiff = Math.Max(pitch1, pitch2) / Math.Min(pitch1, pitch2) - 1.0f;
|
||||
|
||||
if (fDiff < m_maxOverlapDiff)
|
||||
detectedPitch = (pitch1 + pitch2) * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
// Log the pitch record
|
||||
AddPitchRecord(detectedPitch);
|
||||
|
||||
m_curPitchSamplePos += m_samplesPerPitchBlock;
|
||||
m_curPitchIndex++;
|
||||
}
|
||||
|
||||
samplesProcessed += frameCount;
|
||||
}
|
||||
LastMaxVol = maxVolume;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup
|
||||
/// </summary>
|
||||
private void Setup()
|
||||
{
|
||||
if (m_sampleRate < 1.0f)
|
||||
return;
|
||||
|
||||
m_dsp = new PitchDsp(m_sampleRate, kMinFreq, kMaxFreq);
|
||||
|
||||
m_iirFilterLoLo = new IIRFilter();
|
||||
m_iirFilterLoLo.Proto = IIRFilter.ProtoType.Butterworth;
|
||||
m_iirFilterLoLo.Type = IIRFilter.FilterType.HP;
|
||||
m_iirFilterLoLo.Order = 5;
|
||||
m_iirFilterLoLo.FreqLow = 45.0f;
|
||||
m_iirFilterLoLo.SampleRate = (float)m_sampleRate;
|
||||
|
||||
m_iirFilterLoHi = new IIRFilter();
|
||||
m_iirFilterLoHi.Proto = IIRFilter.ProtoType.Butterworth;
|
||||
m_iirFilterLoHi.Type = IIRFilter.FilterType.LP;
|
||||
m_iirFilterLoHi.Order = 5;
|
||||
m_iirFilterLoHi.FreqHigh = 280.0f;
|
||||
m_iirFilterLoHi.SampleRate = (float)m_sampleRate;
|
||||
|
||||
m_iirFilterHiLo = new IIRFilter();
|
||||
m_iirFilterHiLo.Proto = IIRFilter.ProtoType.Butterworth;
|
||||
m_iirFilterHiLo.Type = IIRFilter.FilterType.HP;
|
||||
m_iirFilterHiLo.Order = 5;
|
||||
m_iirFilterHiLo.FreqLow = 45.0f;
|
||||
m_iirFilterHiLo.SampleRate = (float)m_sampleRate;
|
||||
|
||||
m_iirFilterHiHi = new IIRFilter();
|
||||
m_iirFilterHiHi.Proto = IIRFilter.ProtoType.Butterworth;
|
||||
m_iirFilterHiHi.Type = IIRFilter.FilterType.LP;
|
||||
m_iirFilterHiHi.Order = 5;
|
||||
m_iirFilterHiHi.FreqHigh = 2000.0f;
|
||||
m_iirFilterHiHi.SampleRate = (float)m_sampleRate;
|
||||
|
||||
m_detectOverlapSamples = (int)(kDetectOverlapSec * m_sampleRate);
|
||||
m_maxOverlapDiff = kMaxOctaveSecRate * kDetectOverlapSec;
|
||||
|
||||
m_pitchBufSize = (int)(((1.0f / (float)kMinFreq) * 2.0f + ((kAvgCount - 1) * kAvgOffset)) * m_sampleRate) + 16;
|
||||
m_pitchBufLo = new float[m_pitchBufSize + m_detectOverlapSamples];
|
||||
m_pitchBufHi = new float[m_pitchBufSize + m_detectOverlapSamples];
|
||||
m_samplesPerPitchBlock = (int)Math.Round(m_sampleRate / m_pitchRecordsPerSecond);
|
||||
|
||||
m_circularBufferLo = new CircularBuffer<float>((int)(kCircularBufSaveTime * m_sampleRate + 0.5f) + 10000);
|
||||
m_circularBufferHi = new CircularBuffer<float>((int)(kCircularBufSaveTime * m_sampleRate + 0.5f) + 10000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The pitch was detected - add the record
|
||||
/// </summary>
|
||||
/// <param name="pitch"></param>
|
||||
private void AddPitchRecord(float pitch)
|
||||
{
|
||||
int midiNote = 0;
|
||||
int midiCents = 0;
|
||||
|
||||
PitchDsp.PitchToMidiNote(pitch, out midiNote, out midiCents);
|
||||
|
||||
var record = new PitchRecord();
|
||||
|
||||
record.RecordIndex = m_curPitchIndex;
|
||||
record.Pitch = pitch;
|
||||
record.MidiNote = midiNote;
|
||||
record.MidiCents = midiCents;
|
||||
|
||||
m_curPitchRecord = record;
|
||||
|
||||
if (m_recordPitchRecords)
|
||||
{
|
||||
if (m_pitchRecordHistorySize > 0 && m_pitchRecords.Count >= m_pitchRecordHistorySize)
|
||||
m_pitchRecords.RemoveAt(0);
|
||||
|
||||
m_pitchRecords.Add(record);
|
||||
}
|
||||
|
||||
if (this.PitchDetected != null)
|
||||
this.PitchDetected(this, record);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores one record
|
||||
/// </summary>
|
||||
public struct PitchRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// The index of the pitch record since the last Reset call
|
||||
/// </summary>
|
||||
public int RecordIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The detected pitch
|
||||
/// </summary>
|
||||
public float Pitch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The detected MIDI note, or 0 for no pitch
|
||||
/// </summary>
|
||||
public int MidiNote { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The offset from the detected MIDI note in cents, from -50 to +50.
|
||||
/// </summary>
|
||||
public int MidiCents { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper restore InconsistentNaming
|
||||
}
|
||||
@@ -16,47 +16,33 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Vocaluxe.Base;
|
||||
|
||||
namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
{
|
||||
class CPortAudioRecord : CPortAudioCommon, IRecord
|
||||
class CPortAudioRecord : CRecordBase, IRecord
|
||||
{
|
||||
private bool _Initialized;
|
||||
private readonly List<CRecordDevice> _Devices = new List<CRecordDevice>();
|
||||
|
||||
private CPortAudioHandle _PaHandle;
|
||||
private PortAudioSharp.PortAudio.PaStreamCallbackDelegate _MyRecProc;
|
||||
private IntPtr[] _RecHandle;
|
||||
|
||||
private readonly CBuffer[] _Buffer;
|
||||
|
||||
public CPortAudioRecord()
|
||||
{
|
||||
_Buffer = new CBuffer[CSettings.MaxNumPlayer];
|
||||
for (int i = 0; i < _Buffer.Length; i++)
|
||||
_Buffer[i] = new CBuffer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Init PortAudio and list record devices
|
||||
/// </summary>
|
||||
/// <returns>true if success</returns>
|
||||
public bool Init()
|
||||
public override bool Init()
|
||||
{
|
||||
if (_Initialized)
|
||||
if (!base.Init())
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
if (!_InitDriver())
|
||||
return false;
|
||||
_PaHandle = new CPortAudioHandle();
|
||||
|
||||
_Devices.Clear();
|
||||
int hostAPI = _GetHostApi();
|
||||
int hostAPI = _PaHandle.GetHostApi();
|
||||
int numDevices = PortAudioSharp.PortAudio.Pa_GetDeviceCount();
|
||||
for (int i = 0; i < numDevices; i++)
|
||||
{
|
||||
@@ -69,9 +55,6 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
}
|
||||
}
|
||||
|
||||
foreach (CBuffer buffer in _Buffer)
|
||||
buffer.Reset();
|
||||
|
||||
_RecHandle = new IntPtr[_Devices.Count];
|
||||
_MyRecProc = _MyPaStreamCallback;
|
||||
_Initialized = true;
|
||||
@@ -80,6 +63,7 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
{
|
||||
_Initialized = false;
|
||||
CLog.LogError("Error initializing PortAudio: " + e.Message);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -95,9 +79,6 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
if (!_Initialized)
|
||||
return false;
|
||||
|
||||
if (_RecHandle == null || _RecHandle.Length == 0)
|
||||
return false;
|
||||
|
||||
Stop();
|
||||
|
||||
foreach (IntPtr handle in _RecHandle)
|
||||
@@ -128,7 +109,7 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
suggestedLatency = PortAudioSharp.PortAudio.Pa_GetDeviceInfo(_Devices[dev].ID).defaultLowInputLatency,
|
||||
hostApiSpecificStreamInfo = IntPtr.Zero
|
||||
};
|
||||
if (!_OpenInputStream(
|
||||
if (!_PaHandle.OpenInputStream(
|
||||
out _RecHandle[dev],
|
||||
ref inputParams,
|
||||
44100,
|
||||
@@ -138,7 +119,7 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
new IntPtr(dev)))
|
||||
return false;
|
||||
|
||||
if (_CheckError("Start Stream (rec)", PortAudioSharp.PortAudio.Pa_StartStream(_RecHandle[dev])))
|
||||
if (_PaHandle.CheckError("Start Stream (rec)", PortAudioSharp.PortAudio.Pa_StartStream(_RecHandle[dev])))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -162,97 +143,25 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
/// <summary>
|
||||
/// Stop all voice capturing streams and terminate PortAudio
|
||||
/// </summary>
|
||||
public void Close()
|
||||
public override void Close()
|
||||
{
|
||||
if (!_Initialized)
|
||||
return;
|
||||
|
||||
foreach (IntPtr handle in _RecHandle)
|
||||
_CloseStream(handle);
|
||||
_CloseDriver();
|
||||
if (_RecHandle != null)
|
||||
{
|
||||
foreach (IntPtr handle in _RecHandle)
|
||||
{
|
||||
if (handle != IntPtr.Zero)
|
||||
_PaHandle.CloseStream(handle);
|
||||
}
|
||||
}
|
||||
if (_PaHandle != null)
|
||||
{
|
||||
_PaHandle.Close();
|
||||
_PaHandle = null;
|
||||
}
|
||||
|
||||
_Initialized = false;
|
||||
|
||||
//System.IO.File.WriteAllBytes("test0.raw", _Buffer[0].Buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect Pitch and Volume of the newest voice buffer
|
||||
/// </summary>
|
||||
/// <param name="player"></param>
|
||||
public void AnalyzeBuffer(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return;
|
||||
|
||||
_Buffer[player].AnalyzeBuffer();
|
||||
}
|
||||
|
||||
public int GetToneAbs(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return 0;
|
||||
|
||||
return _Buffer[player].ToneAbs;
|
||||
}
|
||||
|
||||
public int GetTone(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return 0;
|
||||
|
||||
return _Buffer[player].Tone;
|
||||
}
|
||||
|
||||
public void SetTone(int player, int tone)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return;
|
||||
|
||||
_Buffer[player].Tone = tone;
|
||||
}
|
||||
|
||||
public float GetMaxVolume(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return 0f;
|
||||
|
||||
return _Buffer[player].MaxVolume;
|
||||
}
|
||||
|
||||
public bool ToneValid(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return false;
|
||||
|
||||
return _Buffer[player].ToneValid;
|
||||
}
|
||||
|
||||
public int NumHalfTones()
|
||||
{
|
||||
if (!_Initialized)
|
||||
return 0;
|
||||
|
||||
return CBuffer.NumHalfTones;
|
||||
}
|
||||
|
||||
public float[] ToneWeigth(int player)
|
||||
{
|
||||
if (!_Initialized)
|
||||
return null;
|
||||
|
||||
return _Buffer[player].ToneWeigth;
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<CRecordDevice> RecordDevices()
|
||||
{
|
||||
if (!_Initialized)
|
||||
return null;
|
||||
|
||||
if (_Devices.Count == 0)
|
||||
return null;
|
||||
|
||||
return _Devices.AsReadOnly();
|
||||
base.Close();
|
||||
}
|
||||
|
||||
private PortAudioSharp.PortAudio.PaStreamCallbackResult _MyPaStreamCallback(
|
||||
@@ -275,34 +184,10 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||
numBytes = frameCount * 2;
|
||||
|
||||
byte[] recbuffer = new byte[numBytes];
|
||||
byte[] leftBuffer;
|
||||
byte[] rightBuffer;
|
||||
|
||||
// copy from managed to unmanaged memory
|
||||
Marshal.Copy(input, recbuffer, 0, (int)numBytes);
|
||||
if (dev.Channels == 2)
|
||||
{
|
||||
leftBuffer = new byte[numBytes / 2];
|
||||
rightBuffer = new byte[numBytes / 2];
|
||||
//[]: Sample, L: Left channel R: Right channel
|
||||
//[LR][LR][LR][LR][LR][LR]
|
||||
//The data is interleaved and needs to be demultiplexed
|
||||
for (int i = 0; i < frameCount; i++)
|
||||
{
|
||||
leftBuffer[i * 2] = recbuffer[i * 4];
|
||||
leftBuffer[i * 2 + 1] = recbuffer[i * 4 + 1];
|
||||
rightBuffer[i * 2] = recbuffer[i * 4 + 2];
|
||||
rightBuffer[i * 2 + 1] = recbuffer[i * 4 + 3];
|
||||
}
|
||||
}
|
||||
else
|
||||
leftBuffer = rightBuffer = recbuffer;
|
||||
|
||||
if (dev.PlayerChannel1 > 0)
|
||||
_Buffer[dev.PlayerChannel1 - 1].ProcessNewBuffer(leftBuffer);
|
||||
|
||||
if (dev.PlayerChannel2 > 0)
|
||||
_Buffer[dev.PlayerChannel2 - 1].ProcessNewBuffer(rightBuffer);
|
||||
_HandleData(dev, recbuffer);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -222,7 +222,8 @@
|
||||
<Compile Include="Lib\Sound\Playback\GstreamerSharp\CGstreamerSharpAudioStream.cs" />
|
||||
<Compile Include="Lib\Sound\Playback\OpenAL\COpenAlStream.cs" />
|
||||
<Compile Include="Lib\Sound\Playback\PortAudio\CPortAudioStream.cs" />
|
||||
<Compile Include="Lib\Sound\CPortAudioCommon.cs" />
|
||||
<Compile Include="Lib\Sound\CPortAudioHandle.cs" />
|
||||
<Compile Include="Lib\Sound\Record\PitchTracker\CAnalyzer.cs" />
|
||||
<Compile Include="Lib\Sound\Record\CBuffer.cs" />
|
||||
<Compile Include="Base\CConfig.cs" />
|
||||
<Compile Include="Base\CCover.cs" />
|
||||
@@ -264,8 +265,19 @@
|
||||
<Compile Include="GameModes\CGameModes.cs" />
|
||||
<Compile Include="Lib\Input\CControllerFramework.cs" />
|
||||
<Compile Include="Lib\Sound\Playback\GstreamerSharp\CGstreamerSharpAudio.cs" />
|
||||
<Compile Include="Lib\Sound\Record\CRecordBase.cs" />
|
||||
<Compile Include="Lib\Sound\Record\DirectSound\CSampleDataEventArgs.cs" />
|
||||
<Compile Include="Lib\Sound\Record\DirectSound\CSoundCardSource.cs" />
|
||||
<Compile Include="Lib\Sound\Record\PitchTracker\CPitchTrackerTest.cs" />
|
||||
<Compile Include="Lib\Sound\Record\PitchTracker\CPtAKF.cs" />
|
||||
<Compile Include="Lib\Sound\Record\PitchTracker\CPtDyWa.cs" />
|
||||
<Compile Include="Lib\Sound\Record\PitchTracker\CPtSharp.cs" />
|
||||
<Compile Include="Lib\Sound\Record\PitchTracker\IPitchTracker.cs" />
|
||||
<Compile Include="Lib\Sound\Record\PitchTracker\Pitch\CircularBuffer.cs" />
|
||||
<Compile Include="Lib\Sound\Record\PitchTracker\Pitch\Extensions.cs" />
|
||||
<Compile Include="Lib\Sound\Record\PitchTracker\Pitch\IIRFilter.cs" />
|
||||
<Compile Include="Lib\Sound\Record\PitchTracker\Pitch\PitchDsp.cs" />
|
||||
<Compile Include="Lib\Sound\Record\PitchTracker\Pitch\PitchTracker.cs" />
|
||||
<Compile Include="Lib\Video\Acinerella\CDecoder.cs" />
|
||||
<Compile Include="Lib\Video\Acinerella\CDecoderThread.cs" />
|
||||
<Compile Include="Lib\Video\Acinerella\CFramebuffer.cs" />
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace VocaluxeLib.Utils
|
||||
{
|
||||
//Simple class to read and write WAV files
|
||||
public class CWavFile
|
||||
{
|
||||
public short NumChannels { get; private set; }
|
||||
public int SampleRate { get; private set; }
|
||||
public short BitsPerSample { get; private set; }
|
||||
public string FileName { get; private set; }
|
||||
//Unless this is true, all fields are to be considered as invalid
|
||||
public int DataSize { get; private set; }
|
||||
//Number of samples (per channel)
|
||||
public int NumSamples
|
||||
{
|
||||
get { return (DataSize / (BitsPerSample / 8 * NumChannels)); }
|
||||
}
|
||||
//Number of samples left to be read (per channel)
|
||||
public int NumSamplesLeft { get; private set; }
|
||||
public bool IsOpen { get; private set; }
|
||||
private int _DataPos;
|
||||
private FileStream _File;
|
||||
private bool _IsWritable;
|
||||
|
||||
public CWavFile()
|
||||
{
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
~CWavFile()
|
||||
{
|
||||
Debug.Assert(!IsOpen);
|
||||
Close();
|
||||
}
|
||||
|
||||
public void Create(string fileName, short numChannels, int sampleRate, short bitsPerSample)
|
||||
{
|
||||
if (IsOpen)
|
||||
Close();
|
||||
_File = new FileStream(fileName, FileMode.Create);
|
||||
FileName = fileName;
|
||||
NumChannels = numChannels;
|
||||
SampleRate = sampleRate;
|
||||
BitsPerSample = bitsPerSample;
|
||||
DataSize = 0;
|
||||
NumSamplesLeft = 0;
|
||||
IsOpen = true;
|
||||
_IsWritable = true;
|
||||
_WriteHeader();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (!IsOpen)
|
||||
return;
|
||||
if (_IsWritable)
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(_File);
|
||||
writer.Seek(4, SeekOrigin.Begin);
|
||||
writer.Write((int)_File.Length - 8);
|
||||
writer.Seek(_DataPos - 4, SeekOrigin.Begin);
|
||||
writer.Write(DataSize);
|
||||
}
|
||||
_File.Dispose();
|
||||
_File = null;
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
public bool Open(string fileName)
|
||||
{
|
||||
if (IsOpen)
|
||||
Close();
|
||||
if (!File.Exists(fileName))
|
||||
return false;
|
||||
FileName = fileName;
|
||||
_File = new FileStream(FileName, FileMode.Open);
|
||||
try
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(_File);
|
||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "RIFF")
|
||||
return false;
|
||||
reader.ReadInt32(); //Chunksize (file)
|
||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "WAVE")
|
||||
return false;
|
||||
long curPos = _File.Position;
|
||||
if (_SeekTo(reader, "fmt ") < 0)
|
||||
return false;
|
||||
short audioFormat = reader.ReadInt16();
|
||||
if (audioFormat != 1) //PCM
|
||||
return false;
|
||||
NumChannels = reader.ReadInt16();
|
||||
SampleRate = reader.ReadInt32();
|
||||
int bytesPerSec = reader.ReadInt32();
|
||||
short blockAlign = reader.ReadInt16();
|
||||
BitsPerSample = reader.ReadInt16();
|
||||
if (BitsPerSample != 8 && BitsPerSample != 16)
|
||||
return false;
|
||||
if (blockAlign != NumChannels * (BitsPerSample / 8))
|
||||
return false;
|
||||
if (bytesPerSec != blockAlign * SampleRate)
|
||||
return false;
|
||||
_File.Position = curPos;
|
||||
DataSize = _SeekTo(reader, "data");
|
||||
if (DataSize <= 0)
|
||||
return false;
|
||||
_DataPos = (int)_File.Position;
|
||||
DataSize = Math.Min(DataSize, (int)_File.Length - _DataPos);
|
||||
NumSamplesLeft = NumSamples;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_File.Dispose();
|
||||
throw;
|
||||
}
|
||||
IsOpen = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of numSamples samples per channel for the selected channel(s)
|
||||
/// </summary>
|
||||
/// <param name="numSamples">Samples per channel</param>
|
||||
/// <param name="channel">Channel for which to get the samples (0=all)</param>
|
||||
/// <returns></returns>
|
||||
public byte[] GetNextSamples8Bit(int numSamples, int channel = 0)
|
||||
{
|
||||
if (channel < 0 || channel > NumChannels || BitsPerSample != 8)
|
||||
return null;
|
||||
if (numSamples > NumSamplesLeft)
|
||||
return null;
|
||||
byte[] result;
|
||||
BinaryReader reader = new BinaryReader(_File);
|
||||
if (channel == 0 || NumChannels == 1)
|
||||
result = reader.ReadBytes(numSamples * NumChannels);
|
||||
else
|
||||
{
|
||||
result = new byte[numSamples];
|
||||
for (int i = 0; i < numSamples; i++)
|
||||
{
|
||||
_File.Seek(channel - 1, SeekOrigin.Current);
|
||||
result[i] = reader.ReadByte();
|
||||
_File.Seek(NumChannels - channel, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
NumSamplesLeft -= numSamples;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of numSamples samples per channel for the selected channel(s)
|
||||
/// </summary>
|
||||
/// <param name="numSamples">Samples per channel</param>
|
||||
/// <param name="channel">Channel for which to get the samples (0=all)</param>
|
||||
/// <returns></returns>
|
||||
public short[] GetNextSamples16Bit(int numSamples, int channel = 0)
|
||||
{
|
||||
if (channel < 0 || channel > NumChannels || BitsPerSample != 16)
|
||||
return null;
|
||||
if (numSamples > NumSamplesLeft)
|
||||
return null;
|
||||
short[] result;
|
||||
BinaryReader reader = new BinaryReader(_File);
|
||||
try
|
||||
{
|
||||
if (channel == 0 || NumChannels == 1)
|
||||
{
|
||||
result = new short[numSamples * NumChannels];
|
||||
for (int i = 0; i < numSamples * NumChannels; i++)
|
||||
result[i] = reader.ReadInt16();
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new short[numSamples];
|
||||
for (int i = 0; i < numSamples; i++)
|
||||
{
|
||||
_File.Seek((channel - 1) * 2, SeekOrigin.Current);
|
||||
result[i] = reader.ReadInt16();
|
||||
_File.Seek((NumChannels - channel) * 2, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
NumSamplesLeft -= numSamples;
|
||||
}
|
||||
catch (EndOfStreamException e)
|
||||
{
|
||||
Console.WriteLine("Error: " + e);
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] GetNextSamples16BitAsBytes(int numSamples, int channel = 0)
|
||||
{
|
||||
short[] samples = GetNextSamples16Bit(numSamples, channel);
|
||||
if (samples == null)
|
||||
return null;
|
||||
byte[] samplesByte = new byte[samples.Length * 2];
|
||||
Buffer.BlockCopy(samples, 0, samplesByte, 0, samplesByte.Length);
|
||||
return samplesByte;
|
||||
}
|
||||
|
||||
public bool WriteSamples(byte[] samples, int channel = 0)
|
||||
{
|
||||
if (!_IsWritable || channel < 0 || channel > NumChannels || BitsPerSample != 8)
|
||||
return false;
|
||||
BinaryWriter writer = new BinaryWriter(_File);
|
||||
if (channel == 0 || NumChannels == 1)
|
||||
writer.Write(samples);
|
||||
else
|
||||
{
|
||||
foreach (byte sample in samples)
|
||||
{
|
||||
_File.Seek(channel - 1, SeekOrigin.Current);
|
||||
writer.Write(sample);
|
||||
_File.Seek(NumChannels - channel, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
DataSize += samples.Length;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Write16BitSamples(short[] samples, int channel = 0)
|
||||
{
|
||||
if (!_IsWritable || channel < 0 || channel > NumChannels || BitsPerSample != 16)
|
||||
return false;
|
||||
BinaryWriter writer = new BinaryWriter(_File);
|
||||
if (channel == 0 || NumChannels == 1)
|
||||
{
|
||||
foreach (short sample in samples)
|
||||
writer.Write(sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (short sample in samples)
|
||||
{
|
||||
_File.Seek((channel - 1) * 2, SeekOrigin.Current);
|
||||
writer.Write(sample);
|
||||
_File.Seek((NumChannels - channel) * 2, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
DataSize += samples.Length * 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Write16BitSamples(byte[] samples, int channel = 0)
|
||||
{
|
||||
if (samples.Length == 0)
|
||||
return true;
|
||||
short[] samplesShort = new short[samples.Length / 2];
|
||||
Buffer.BlockCopy(samples, 0, samplesShort, 0, samples.Length);
|
||||
return Write16BitSamples(samplesShort, channel);
|
||||
}
|
||||
|
||||
private void _WriteHeader()
|
||||
{
|
||||
if (!IsOpen || !_IsWritable)
|
||||
return;
|
||||
BinaryWriter writer = new BinaryWriter(_File);
|
||||
writer.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||
writer.Write(0);
|
||||
writer.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
writer.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
writer.Write(16);
|
||||
writer.Write((short)1); // PCM
|
||||
writer.Write(NumChannels); // Channels
|
||||
writer.Write(SampleRate); // Sample rate
|
||||
writer.Write(SampleRate * NumChannels * (BitsPerSample / 8)); // Average bytes per second
|
||||
writer.Write((short)((BitsPerSample / 8) * NumChannels)); // block align
|
||||
writer.Write(BitsPerSample); // bits per sample
|
||||
writer.Write(Encoding.ASCII.GetBytes("data"));
|
||||
writer.Write(DataSize);
|
||||
_DataPos = (int)_File.Position;
|
||||
writer.Seek(4, SeekOrigin.Begin);
|
||||
writer.Write((int)writer.BaseStream.Length);
|
||||
writer.Seek(_DataPos, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
private static int _SeekTo(BinaryReader reader, string chunkId)
|
||||
{
|
||||
do
|
||||
{
|
||||
string curChunkId = Encoding.ASCII.GetString(reader.ReadBytes(4));
|
||||
int chunkSize = reader.ReadInt32();
|
||||
if (curChunkId == chunkId)
|
||||
return chunkSize;
|
||||
reader.BaseStream.Seek(chunkSize, SeekOrigin.Current);
|
||||
} while (reader.BaseStream.Position < reader.BaseStream.Length);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,6 +134,7 @@
|
||||
<Compile Include="PartyModes\CScreenPartyDummy.cs" />
|
||||
<Compile Include="PartyModes\IPartyMode.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Utils\CWavFile.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
|
||||
Reference in new issue
Block a user