문제

나는 iPhone을위한 작은 소프트웨어 신디사이저를 썼습니다.
성능을 더 조정하기 위해 Shark로 응용 프로그램을 측정 한 결과 Float/SINT16 변환에서 많은 시간을 잃고 있음을 발견했습니다.
따라서 "즉시 사용 가능한"SINT16 샘플을 반환하는 조회 테이블을 미리 계산하여 전환을 돌리기 위해 일부 부품을 다시 작성했습니다. 이것은 지금까지 잘 작동합니다.
현재 정수 산술 만 사용하기 위해 일부 필터와 ADSR 봉투 구현을 다시 작성하려고하지만 플로트없이 곱셈/부서를 수행하는 방법을 사용할 수 있습니다.
나는 그것을 목표로하고있다 iPhone 표준 형식:

  • LPCM
  • 16 비트 정수 샘플

플로트를 사용하지 않고 최종 샘플에 진폭을 적용하는 좋은 접근법은 무엇입니까?

편집하다:
내가 지금까지 알아 낸 유일한 것은 현재 샘플을 오른쪽으로 이동시켜 2의 힘으로 나눌 수 있다는 것입니다.

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

그러나 나는 그것으로 부드러운 ADSR 봉투를 만들 수있는 우아한 방법을 생각할 수 없습니다.

edit2 :모든 위대한 답변에 감사드립니다!
내 현재 접근법 :

  • 내 모든 ADSR 봉투 값을 양수 SINT16 범위로 가져 오십시오.
  • 파동 가능한 전류 값을 곱합니다 (SINT32로 중간체를 저장).
  • 결과를 16으로 오른쪽으로 바꿉니다

이것은 작동하는 것 같습니다 :)

도움이 되었습니까?

해결책

이 경우 16 비트를 사용하고 있기 때문에 고정 지점이 좋습니다. 가장 간단한 방법은 필요한 정밀도에 따라 10의 전력을 곱하는 것입니다. 32 비트 int를 중간체로 사용할 수 있다면 괜찮은 정밀도를 얻을 수 있어야합니다. 결국 당신은 당신이 원하는대로 16 비트 int, 반올림 또는 잘림으로 되돌릴 수 있습니다.

편집 : 값을 더 크게 만들기 위해 왼쪽으로 이동하려고합니다. 더 정밀한 유형의 전환 결과를 유형에 저장하십시오 (필요한 것에 따라 32 또는 64 비트). 서명 된 유형을 사용하는 경우 간단한 이동이 작동하지 않습니다.

두 개의 고정 점 번호를 곱하거나 나누는지 조심하십시오. 바람이 곱하는 것 (a * n) * (bn) 그리고 당신은 aa 대신 n^2N. 부서는 (an) / (bn) (a/b) 대신 ((an)/b). 그렇기 때문에 10의 Powers를 사용하는 것을 제안했는데, 고정 지점에 익숙하지 않은 경우 실수를 쉽게 찾을 수 있습니다.

계산이 완료되면 오른쪽으로 돌아가서 16 비트 int로 돌아갑니다. 당신이 공상을 원한다면, 당신은 당신이 이동하기 전에 반올림을 할 수도 있습니다.

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

다른 팁

답변 이렇게 질문 구현 측면에서 매우 포괄적입니다. 여기저기서 본 것보다 조금 더 설명이 있습니다.

한 가지 방법은 모든 숫자를 범위로 강제하는 것입니다. [-1.0,1.0). 그런 다음 해당 숫자를 [-2^15, (2^15) -1] 범위에 매핑합니다. 예를 들어,

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

이 두 숫자를 곱하면 얻을 수 있습니다

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

마지막 줄에서 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

이것이 바로 산술입니다. 구현의 경우 2 개의 16 비트 정수의 곱하기에는 32 비트 결과가 필요하므로 임시는 32 비트 여야합니다. 또한 32768은 16 비트 변수로 표현할 수 없으므로 컴파일러가 32 비트 직접 즉시 생성됩니다. 그리고 이미 언급했듯이 2의 전력으로 곱하기/나누기로 전환하여 쓸 수 있습니다.

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)이 올바른 범위가 아니라고 가정 해 봅시다. 예를 들어 숫자를 [-4.0,4.0)으로 제한하고 싶다면 n = 13을 사용할 수 있습니다. 그러면 이진 지점 앞에 1 개의 부호 비트, 2 비트, 다음 13 개가 있습니다. 이를 각각 1.15 및 3.13 고정점 분수 유형이라고합니다. 당신은 헤드 룸의 분수로 정밀도를 거래합니다.

분수 유형을 추가하고 빼는 것은 채도를 찾는 한 잘 작동합니다. Patros가 말했듯이, 스케일링은 실제로 취소됩니다. 그래서 당신은해야합니다

Quotient = (x1/x2) << N;

또는 정밀도를 보존하기 위해

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

정수로 곱하고 나누는 것은 정상적으로 작동합니다. 예를 들어 6으로 나누려면 간단히 쓸 수 있습니다.

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

그리고 2의 전력으로 나누는 경우,

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

그러나 전체 숫자를 추가하고 빼는 것은 순진하게 작동하지 않습니다. 먼저 정수가 XY 유형에 맞는지 확인하고 동등한 분수 유형을 만들고 진행해야합니다.

나는 이것이 아이디어에 도움이되기를 바랍니다. 깨끗한 구현에 대한 다른 질문의 코드를 살펴보십시오.

빠른 곱셈 알고리즘을 설명하는이 페이지를 살펴보십시오.

http://www.newton.dep.anl.gov/askasci/math99/math99199.htm

일반적으로 서명 된 16.16 고정점 표현을 사용한다고 가정 해 봅시다. 32 비트 정수에 서명 된 16 비트 정수 부품과 16 비트 분수 부품이 있습니다. 그런 다음 iPhone 개발에 어떤 언어가 사용되는지 모르겠지만 (대상 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 () i에서는 분할 전에 정밀도를 유지하기 위해 배수하지만 오페라에 따라 작동이 오버플로 될 수 있습니다. 64 비트 중간체를 사용하여이를 극복 할 수 있습니다. 또한 분열은 정수 부서를 사용하기 때문에 항상 반올림됩니다. 이는 최상의 성능을 제공하지만 반복 계산의 정밀도에 영향을 줄 수 있습니다. 수정 사항은 간단하지만 오버 헤드에 추가됩니다.

일정한 두 가지 전력으로 곱하거나 나눌 때 대부분의 컴파일러는 사소한 최적화를 발견하고 시프트를 사용합니다. 그러나 C는 음수 서명 된 정수의 오른쪽 편이에 대한 동작을 정의하지 않으므로 안전과 휴대 성을 위해 컴파일러에 맡겼습니다. 사용중인 언어에 대한 YMV.

OO 언어에서 고정 된 16q16_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) ;
}

이것이 기본이며, 더 정교하고 트리그 및 기타 수학 기능을 추가 할 수 있습니다.

고정 첨가 및 뺄셈은 내장 + 및 - 연산자 및 해당 변형과 함께 작동합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top