3

나는 iPhone 용 작은 소프트웨어 신디를 썼다.
성능을 더욱 향상시키기 위해 Shark과의 어플리케이션을 측정하여 float/SInt16 변환에서 많은 시간을 낭비한다는 것을 알았습니다.
"사용할 준비가 된"SInt16 샘플을 반환하는 조회 테이블을 사전 계산하여 전환을 해결하기 위해 일부 부품을 다시 작성했습니다. 지금까지 잘 작동합니다.
현재 정수 연산만을 사용하기 위해 일부 필터와 ADSR 엔벨로프 구현을 다시 작성하려고하지만 부동 소수점없이 곱셈/나누기를 수행하는 방법을 일부 tipps 사용할 수 있습니다.부동 소수점 연산 피하기

  • LPCM
  • 16 비트 정수 샘플
  • float를 사용하지 않고 내 최종 샘플 진폭을 적용하는 좋은 방법은 무엇

:
나는 iPhone canonical format을 표적으로 무엇입니까?

편집 :
나는 내 현재 샘플을 마우스 오른쪽 버튼으로 이동하여 2의 거듭 제곱으로 나눌 수있다, 지금까지 IS 알아 낸 유일한 것은.

inBuffer[frame] = wavetable[i % cycleLengthInSamples] >> 4; 

그러나 나는 우아한 ADSR 봉투를 만드는 우아한 방법을 생각할 수 없습니다.

편집 2 : 모든 훌륭한 답변을 보내 주셔서 감사합니다.
나의 현재 접근 :

  • 내 모든 ADSR 엔벨로프를 가지고는 (SInt32로서 저장 중간체)에 전류 웨이브 테이블의 값 × 16
  • 시프트 결과와 함께
  • 곱셈 양 SInt16 범위로 값 권리

이 일을 :)

답변

4

고정 점이이에 있기 때문에 좋은 것 같다 16 비트를 사용하는 경우. 가장 간단한 방법은 필요한 정밀도에 따라 10의 제곱으로 곱하는 것입니다. 32 비트 정수를 중간 값으로 사용할 수 있다면 괜찮은 정밀도를 얻을 수 있어야합니다. 마지막으로 원하는대로 반올림하거나 자르는 16 비트 int로 다시 변환 할 수 있습니다.

편집 : 값을 더 크게 만들려면 왼쪽으로 이동하고 싶습니다. 이동 결과를 더 정밀도가 높은 형식 (필요한 것에 따라 32 또는 64 비트)으로 저장하십시오. 부호가있는 유형을 사용하는 경우 간단한 이동이 작동하지 않습니다.

두 개의 고정 소수점을 곱하거나 나누는 것에주의하십시오. 곱하기는 (a * n) * (b n)이되고, b 대신에 b n^2가됩니다. 나눗셈은 ( n)/(b) 대신 (a/b) 인 ( n)/(b n)입니다. 그래서 10의 제곱을 사용하는 것이 좋습니다. 고정 소수점에 익숙하지 않은 경우 실수를 쉽게 찾을 수 있습니다.

계산이 끝나면 다시 오른쪽으로 이동하여 16 비트 정수로 돌아갑니다. 마음 만 먹고 싶다면 이동하기 전에 반올림을 할 수 있습니다.

효율적인 고정 소수점을 구현하는 데 정말로 관심이 있다면 약간의 독서를하는 것이 좋습니다. http://www.digitalsignallabs.com/fp.pdf

+0

그건 내가 생각한거야. 하지만 고정 점을 얻으려면 2의 힘으로 번식하지 않겠습니까? 그것은 단지 비트를 이동하는 것을 포함합니다. –

+0

부호있는 int를 사용할 때 쉬프트가 펑키 할 수 있습니다. 2의 제곱은 더 효율적이지만 디버그하기는 더 어렵습니다. 고정 소수점을 처음 사용하는 경우 10의 제곱을 추천합니다. – patros

+0

답변 해 주셔서 감사합니다. float-less ADSR 봉투를 구현하는 우아한 방법을 아직 찾지 못했습니다. 방금 ​​샘플을 오른쪽 시프 팅하여 임의의 2의 제곱으로 나눗셈을 시도했기 때문에 내 진폭이 줄어 듭니다. 그러나 그와 함께 부드러운 봉투를 만드는 방법을 알 수는 없습니다. –

3

this SO question에 대한 답변은 구현의 측면에서 매우 포괄적이다. 저기서 보았던 것보다 더 많은 설명이 있습니다 :

하나의 접근법은 모든 숫자를 [-1.0,1.0]과 같이 범위로 강제하는 것입니다. 그런 다음 해당 숫자를 [-2^15, (2^15) -1] 범위에 매핑합니다. 이 두 숫자를 곱하면 예를 들어,

Half = round(0.5*32768); //16384 
Third = round((1.0/3.0)*32768); //10923 

당신이 마지막 줄에 32768에 의해

Temp = Half*Third; //178962432 
Result = Temp/32768; //5461 = round(1.0/6.0)*32768 

디바이 딩을 얻을은 추가 확장 단계를 필요로 곱셈에 대해 만든 점 Patros입니다. 이것은 2^N 스케일링을 명시 적으로 작성하는 것이 더 합리적입니다.

x1 = x1Float*(2^15); 
x2 = x2Float*(2^15); 
Temp = x1Float*x2Float*(2^15)*(2^15); 
Result = Temp/(2^15); //get back to 2^N scaling 

그래서 산술입니다. 구현을 위해 두 개의 16 비트 정수를 곱하면 32 비트 결과가 필요하므로 Temp는 32 비트 여야합니다. 또한 32768은 16 비트 변수에서 표현할 수 없으므로 컴파일러가 32 비트 바로 가기를 생성합니다. 당신이 이미 언급 한 바와 같이, 당신은 그래서 당신은 권리 범위가 아닌)

N = 15; 
SInt16 x1 = round(x1Float * (1 << N)); 
SInt16 x2 = round(x2Float * (1 << N)); 
SInt32 Temp = x1*x2; 
Result = (SInt16)(Temp >> N); 
FloatResult = ((double)Result)/(1 << N); 

쓰기 그러나 [-1,1을 가정 할 수 2의 거듭 제곱으로/분할을 곱 전환 할 수 있습니다? 숫자를 [-4.0,4.0]으로 제한하려면 N = 13을 사용할 수 있습니다. 그런 다음 1 비트의 부호 비트와 2 진 소수점 이전의 2 비트 및 13 비트의 부호 비트가 있습니다. 이들은 각각 1.15 및 3.13 고정 소수점 분수 유형이라고합니다. 헤드 룸에 대한 분수의 정밀도를 교환합니다.

분수 형식을 더하고 빼면 채도가 떨어질 때까지 잘 작동합니다. 파토 로스 (Patros)가 말했듯이, 분할에 대해서는 실제로 스케일링이 취소됩니다. 그래서 당신은

Quotient = (SInt16)(((SInt32)x1 << N)/x2); //x1 << N needs wide storage 

배가 및 전체 숫자로 나누어 정상적으로 작동 정밀도를 유지하기 위해,

Quotient = (x1/x2) << N; 

을해야하거나. 예를 들어, 정수를 추가 및 뺀, 당신이 간단하게

Quotient = x1/6; //equivalent to x1Float*(2^15)/6, stays scaled 

그리고 2의 거듭 제곱으로 나누어의 경우

를 작성할 수 6

Quotient = x1 >> 3; //divides by 8, can't do x1 << -3 as Patros pointed out 

을 분할하지만, 순진 작동하지 않습니다 . 정수가 x.y 유형에 맞는지, 등가 분수 유형을 만들어 계속 진행하는지 먼저 확인해야합니다.

이 아이디어가 도움이되기를 바랍니다. 깨끗한 구현을위한 다른 질문의 코드를 살펴보십시오.

+0

고마워요! 매우 포괄적입니다. –

1

일반적으로 서명 된 16.16 고정 소수점 표현을 사용한다고 가정 해 보겠습니다. 따라서 32 비트 정수는 부호있는 16 비트 정수 부분과 16 비트 분수 부분을 갖습니다. (? 아마도 목표 - C) 그럼 난 아이폰 개발에 사용되는 어떤 언어 모르겠지만,이 예제는 C에 다음 위의 단순한 구현입니다

#include <stdint.h> 

typedef fixed16q16_t int32_t ; 
#define FIXED16Q16_SCALE 1 << 16 ; 

fixed16q16_t mult16q16(fixed16q16_t a, fixed16q16_t b) 
{ 
    return (a * b)/FIXED16Q16_SCALE ; 
} 

fixed16q16_t div16q16(fixed16q16_t a, fixed16q16_t b) 
{ 
    return (a * FIXED16Q16_SCALE)/b ; 
} 

주, 산술로부터 보호를 제공하지 않습니다 과다. 예를 들어 div16q16()에서는 정밀도를 유지하기 위해 나눗셈 이전에 배수가되지만 피연산자에 따라 연산이 오버 플로우 할 수 있습니다. 이것을 극복하기 위해 64 비트 중간을 사용할 수 있습니다. 또한 나누기는 정수 나누기를 사용하기 때문에 항상 반올림됩니다. 이는 최상의 성능을 제공하지만 반복 계산의 정밀도에 영향을 미칠 수 있습니다. 수정은 간단하지만 오버 헤드를 추가합니다.

2의 거듭 제곱으로 곱하거나 나눌 때 대부분의 컴파일러는 간단한 최적화를 알아보고 시프트를 사용합니다. 그러나 C는 음의 부호있는 정수의 오른쪽 시프트에 대한 동작을 정의하지 않으므로 컴파일러가 안전성과 이식성을 위해 작동하도록 컴파일러에 맡겼습니다. 당신이 사용하는 언어에 관계없이 YMV.

OO 언어에서 fixed16q16_t는 당연히 연산자 오버로딩을 사용하는 클래스의 후보가되므로 일반 산술 유형처럼 사용할 수 있습니다.

당신은 유용한 유형 사이의 변환 찾을 수 있습니다 :

기본입니다
double fixed16q16_to_double(fixed16q16_t fix) 
{ 
    return (double)fix/FIXED16Q16_SCALE ; 
} 

int fixed16q16_to_int(fixed16q16_t fix) 
{ 
    // Note this rounds to nearest rather than truncates 
    return ((fix + FIXED16Q16_SCALE/2))/FIXED16Q16_SCALE ; 
} 

fixed16q16_t int_to_fixed16q16(int i) 
{ 
    return i * FIXED16Q16_SCALE ; 
} 

fixed16q16_t double_to_fixed16q16(double d) 
{ 
    return (int)(d * FIXED16Q16_SCALE) ; 
} 

, 더 정교한 얻고 삼각 및 기타 수학 기능을 추가 할 수 있습니다.

고정 된 더하기 및 빼기는 내장 된 +와 - 연산자와 그 변형으로 작동합니다.