Merge branch 'RecordImprovements' into develop

Conflicts:
	Vocaluxe/Lib/Sound/CPortAudioHandle.cs
	Vocaluxe/Lib/Sound/Record/CBuffer.cs
This commit is contained in:
Flamefire committed 2014-04-29 20:07:56 +02:00
commit 9fe9455eaf
52 files changed
+7169 -497

No files matched your search

+5 -8
View File
@@ -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
+180
View File
@@ -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);
}
+65
View File
@@ -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();
+299
View File
@@ -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
+388
View File
@@ -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];
}
}
+23
View File
@@ -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
+43
View File
@@ -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
}
+24
View File
@@ -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;
}
}
}
*/
+203
View File
@@ -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>
+107
View File
@@ -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>
+233
View File
@@ -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;
}
}
}
*/
+85
View File
@@ -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.
+30
View File
@@ -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
+17
View File
@@ -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
+43
View File
@@ -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 = "&ampltitudeThreshold
//--
}
}
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 = "&ampltitudeThreshold
//--
}
}
}
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
+31
View File
@@ -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);
}
+24
View File
@@ -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;
};
+3
View File
@@ -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
+346
View File
@@ -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.
+84
View File
@@ -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
}
}
+81
View File
@@ -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;
}
+235
View File
@@ -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();
}
+132
View File
@@ -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;
};
+45
View File
@@ -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;
}
+479
View File
@@ -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);
}
*/
+75
View File
@@ -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);
};
+23
View File
@@ -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
+1 -19
View File
@@ -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);
}
+47 -154
View File
@@ -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;
}
}
}
}
+148
View File
@@ -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)
+13 -1
View File
@@ -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" />
+294
View File
@@ -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;
}
}
}
+1
View File
@@ -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>