robert@464: @ robert@464: @ audio_routines.S robert@464: @ robert@464: @ NEON-based functions for time-critical audio processing robert@464: @ robert@464: @ Andrew McPherson 2014 robert@464: @ Queen Mary University of London robert@464: @ robert@464: robert@464: .syntax unified robert@464: .arch armv7-a robert@464: .fpu neon robert@464: robert@464: @ void oscillator_bank_neon(int numAudioFrames, float *audioOut, robert@464: @ int activePartialNum, int lookupTableSize, robert@464: @ float *phases, float *frequencies, float *amplitudes, robert@464: @ float *freqDerivatives, float *ampDerivatives, robert@464: @ float *lookupTable); robert@464: robert@464: @ Registers: robert@464: @ r0: numAudioFrames How many frames to render robert@464: @ r1: audioOut Buffer for audio output samples [stereo] robert@464: @ r2: activePartialNum How many active partials to render robert@464: @ r3: lookupTableSize Size of lookup table robert@464: @ ---- other arguments start on the stack and are moved: ----- robert@464: @ r4: phases Phase of each oscillator (pointer) robert@464: @ r5: frequencies Normalised frequency of each oscillator (pointer) robert@464: @ r6: amplitudes Normalised amplitude of each oscillator (pointer) robert@464: @ r7: freqDerivatives Derivative of frequency for each oscillator (pointer) robert@464: @ r8: ampDerivatives Derivative of amplitude for each oscillator (pointer) robert@464: @ r9: lookupTable Lookup table containing one oscillation robert@464: @ robert@464: @ Alignment requirements: robert@464: @ audioOut: 8-byte boundary robert@464: @ phases: 16-byte boundary robert@464: @ frequencies: 16-byte boundary robert@464: @ amplitudes: 16-byte boundary robert@464: @ freqDerivatives: 16-byte bounary robert@464: @ ampDerivatives: 16-byte boundary robert@464: @ lookupTable: 4-byte boundary (TODO: check this) robert@464: robert@464: .align 2 robert@464: .global oscillator_bank_neon robert@464: .thumb robert@464: .thumb_func robert@464: .type oscillator_bank_neon, %function robert@464: oscillator_bank_neon: robert@464: robert@464: robert@464: dSample .dn D6.F32 robert@464: qPhases .qn Q8.F32 robert@464: dPhases_0 .dn D16.F32 robert@464: dPhases_1 .dn D17.F32 robert@464: qFreqs .qn Q9.F32 robert@464: dFreqs_0 .dn D18.F32 robert@464: dFreqs_1 .dn D19.F32 robert@464: qAmps .qn Q10.F32 robert@464: dAmps_0 .dn D20.F32 robert@464: dAmps_1 .dn D21.F32 robert@464: qFreqDs .qn Q11.F32 robert@464: dFreqDs_0 .dn D22.F32 robert@464: dFreqDs_1 .dn D23.F32 robert@464: qAmpDs .qn Q12.F32 robert@464: dAmpDs_0 .dn D24.F32 robert@464: dAmpDs_1 .dn D25.F32 robert@464: robert@464: qBaseInts .qn Q13.U32 @ Base indexes: unsigned ints x4 robert@464: dBaseInts_0 .dn D26.U32 robert@464: dBaseInts_1 .dn D27.U32 robert@464: qFractions .qn Q14.F32 @ Fraction indexes: floats x4 robert@464: qTableBase .qn Q15.U32 @ Base of lookup table robert@464: robert@464: cmp r0, #0 @ Check for trivial case 1: zero frames robert@464: it eq robert@464: bxeq lr @ Return if that's the case (otherwise might have odd behaviour) robert@464: cmp r2, #4 @ Check for trivial case 2: zero oscillators robert@464: it lt robert@464: bxlt lr @ Return if that's the case robert@464: robert@464: push {r4-r11} @ Now arguments start 32 bytes above SP robert@464: add r11, sp, #32 @ Pointer to 32 bytes into the stack robert@464: ldm r11, {r4-r9} @ Load 6 arguments into registers robert@464: robert@464: vdup qTableBase, r9 @ Move lookup table base index into 4 ints robert@464: robert@464: @ Outer loop: iterate over the number of oscillators, choosing 4 at a robert@464: @ time to work with. robert@464: oscbank_oscillator_loop: robert@464: vld1 {dPhases_0, dPhases_1}, [r4] @ no increment; will store at end of sample loop robert@464: vld1 {dFreqs_0, dFreqs_1}, [r5] robert@464: vld1 {dAmps_0, dAmps_1}, [r6] robert@464: vld1 {dFreqDs_0, dFreqDs_1}, [r7]! @ increment; won't update at end of sample loop robert@464: vld1 {dAmpDs_0, dAmpDs_1}, [r8]! robert@464: robert@464: push {r0-r1,r4-r8} robert@464: @ --- inner loop: iterate over the number of samples --- robert@464: oscbank_sample_loop: robert@464: vcvt qBaseInts, qPhases @ Take floor(phases) robert@464: vmov q2.f32, #1.0 @ Load 1.0 into every slot of q2 robert@464: vshl q0.U32, qBaseInts, #2 @ Shift the indexes left 2 (*4 for float addressing) robert@464: vcvt qFractions, qBaseInts @ int back to float robert@464: vadd q0.U32, q0.U32, qTableBase @ Find memory addresses robert@464: robert@464: vmov r4, r5, d0 @ Move two indexes to ARM registers robert@464: vmov r6, r7, d1 @ Move two more indexes to ARM registers robert@464: vsub qFractions, qPhases, qFractions @ fraction = phase - floor(phase) robert@464: robert@464: vldr.64 d0, [r4] @ Load two consecutive floats at each location robert@464: vldr.64 d1, [r5] @ These hold the previous and following samples in the table robert@464: vldr.64 d2, [r6] @ TODO: check whether these work at 4-byte alignment robert@464: vldr.64 d3, [r7] robert@464: robert@464: @ Format at this point: robert@464: @ Osc0(before) Osc0(after) Osc1(before) Osc1(after) Osc2(before) Osc2(after) Osc3(before) Osc3(after) robert@464: @ We want: robert@464: @ Osc0(before) Osc1(before) Osc2(before) Osc3(before) Osc0(after) Osc1(after) Osc2(after) Osc3(after) robert@464: robert@464: vuzp.32 q0, q1 @ Now q0 contains before, q1 contains after robert@464: vsub q2.f32, q2.f32, qFractions @ q2 = 1.0 - fraction robert@464: vmul q1.f32, q1.f32, qFractions @ q1 = fraction * after robert@464: vmul q0.f32, q0.f32, q2.f32 @ q0 = (1.0 - fraction) * before robert@464: robert@464: vadd qPhases, qPhases, qFreqs @ Update phases robert@464: vadd qFreqs, qFreqs, qFreqDs @ Update frequencies robert@464: robert@464: vadd q0.f32, q0.f32, q1.f32 @ Add two interpolated components to get the final sample robert@464: vdup q2.u32, r3 @ Put lookup table size into each element of q2 robert@464: vcvt qBaseInts, qPhases @ Take floor of new phases robert@464: vmul q0.f32, q0.f32, qAmps @ Multiply samples by current amplitude robert@464: robert@464: vld1 dSample, [r1] @ Load the current stereo samples robert@464: vpadd d2.f32, d0.f32, d1.f32 @ Pairwise accumulate q0 (output sample) into d2 robert@464: robert@464: vand q2, q2, qBaseInts @ Logical AND of new phase int leaves 1 bit set only if phase >= table size robert@464: vpadd d3.f32, d2.f32, d2.f32 @ Pairwise accumulate d2 into d0 --> d0[0] and d0[1] both hold total of 4 oscillators robert@464: vadd qAmps, qAmps, qAmpDs @ Update amplitudes robert@464: vcvt q0.f32, q2.u32 @ Convert int back to float after AND operation robert@464: robert@464: vadd dSample, dSample, d3.f32 @ Add oscillator outputs to each channel robert@464: robert@464: subs r0, r0, #1 @ numFrames-- robert@464: vsub qPhases, qPhases, q0.f32 @ Keep phases in table range robert@464: vst1 dSample, [r1]! @ Store back in buffer and increment by 8 robert@464: robert@464: it gt robert@464: bgt oscbank_sample_loop @ Loop if numFrames > 0 robert@464: robert@464: @ --- end inner loop --- robert@464: pop {r0-r1,r4-r8} @ Restore registers: restores audioOut and numFrames, among others robert@464: robert@464: vst1 {dPhases_0, dPhases_1}, [r4]! @ Store phases back to array robert@464: vst1 {dFreqs_0, dFreqs_1}, [r5]! @ Store frequencies back to array robert@464: vst1 {dAmps_0, dAmps_1}, [r6]! @ Store amplitudes back to array robert@464: @ No need to update r7, r8 robert@464: robert@464: subs r2, r2, #4 @ numPartials -= 4 robert@464: it gt robert@464: bgt oscbank_oscillator_loop @ Loop if numPartials > 0 robert@464: robert@464: pop {r4-r11} robert@464: bx lr robert@464: robert@464: robert@464: @ void wavetable_interpolate_neon(int numSamplesIn, int numSamplesOut, robert@464: @ float *tableIn, float *tableOut); robert@464: robert@464: @ Registers: robert@464: @ r0: numSamplesIn Size of the input table robert@464: @ r1: numSamplesOut Size of the output table robert@464: @ r2: tableIn Pointer to input table robert@464: @ r3: tableOut Pointer to output table robert@464: robert@464: @ Alignment requirements: robert@464: @ tableIn: 8-byte boundary robert@464: @ tableOut: 8-byte boundary robert@464: robert@464: .align 2 robert@464: .global wavetable_interpolate_neon robert@464: .thumb robert@464: .thumb_func robert@464: .type wavetable_interpolate_neon, %function robert@464: wavetable_interpolate_neon: robert@464: @ TODO robert@464: robert@464: bx lr