cat voice keyboard
cat voice keyboard
作っては見たけれど、動的にサンプリング音源をFFT変換するには、
かなり無理があり、実用的ではないかも。
なので、あらかじめ必要な音を全部生成しキーボードに割り付けてみましたが
それはそれで、最初にかなり時間がかかるので、やはり全音サンプリングデータを
取り込んだ方が、早い気がしますね。
なお、PhaseVocoder&FFTは、海外サイト
http://iq12.com/blog/2009/08/25/real-time-pitch-shifting/
より、拝借
情報を頂いた、alumican_netさんに多謝
/**
* Copyright narutohyper ( http://wonderfl.net/user/narutohyper )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/lc1q
*/
package
{
/*
cat voice keyboard
作っては見たけれど、動的にサンプリング音源をFFT変換するには、
かなり無理があり、実用的ではないかも。
なので、あらかじめ必要な音を全部生成しキーボードに割り付けてみましたが
それはそれで、最初にかなり時間がかかるので、やはり全音サンプリングデータを
取り込んだ方が、早い気がしますね。
なお、PhaseVocoder&FFTは、海外サイト
http://iq12.com/blog/2009/08/25/real-time-pitch-shifting/
より、拝借
情報を頂いた、alumican_netさんに多謝
*/
import flash.display.Sprite;
import flash.display.Shape;
import flash.events.Event;
import flash.media.Sound;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.net.*;
import flash.text.*;
import net.hires.debug.Stats;
[SWF(width = 465, height = 465, frameRate = 60, backgroundColor=0x727B80)]
public class catKeyboard extends Sprite {
//private var loadData:String = "http://marubayashi.net/archive/sample/cat/guitar.mp3";
private var loadData:String = "http://marubayashi.net/archive/sample/cat/cat.mp3";
private var source:Sound = new Sound();
private var white:Sprite;
private var black:Sprite;
private var comment:TextField;
private var progressVar:Sprite
public function catKeyboard() {
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.quality = StageQuality.HIGH;
var back:Sprite=new Sprite()
back.graphics.beginFill(0x727B80,1)
back.graphics.drawRect(0,0,465,465)
addChild(back)
addChild(new Stats())
//簡易loadingBar
progressVar = new Sprite()
addChild(progressVar)
progressVar.y=465/2+80
//Bar
for (var i:uint=0;i<24;i++) {
catWalk(i,0.2)
}
comment=new TextField()
comment.autoSize=TextFieldAutoSize.CENTER
comment.selectable=false;
comment.mouseEnabled=false;
var format:TextFormat=new TextFormat();
format.color=0x000000
format.size=12;
format.font='_ゴシック';
format.align='center';
comment.defaultTextFormat=format
comment.x=465/2
comment.y=465/2
addChild(comment)
comment.text="音源生成中\nマシンスペックにもよりますが、かなり時間がかかります。";
source.addEventListener(Event.COMPLETE, loadComplete);
source.load(new URLRequest(loadData));
}
private function loadComplete(event:Event):void {
//鍵盤の作成
var keyArray:Array=[];
white=new Sprite()
black=new Sprite()
white.scaleX=white.scaleY=0.65;
black.scaleX=black.scaleY=0.65;
white.x=4;
black.x=4;
white.y=150;
black.y=150;
var counter:uint=0;
var oldCounter:uint=99;
addEventListener(Event.ENTER_FRAME, onRenderTick);
function setKey():void {
oldCounter=counter
keyArray[counter]=new Keyboard(counter,source)
if (keyArray[counter].color) {
black.addChild(keyArray[counter])
} else {
white.addChild(keyArray[counter])
}
keyArray[counter].addEventListener(Keyboard.MAKE_SOUND_END, nextKey);
keyArray[counter].makeSound()
}
function nextKey():void {
keyArray[counter].removeEventListener(Keyboard.MAKE_SOUND_END, nextKey);
counter++;
if (counter>=24) {
addChild(white)
addChild(black)
removeEventListener(Event.ENTER_FRAME, onRenderTick);
removeChild(comment)
}
}
function onRenderTick(e:Event):void {
if (counter!=oldCounter) {
catWalk(counter,1,0xFFFFFF)
setKey()
}
}
}
private function catWalk(id:uint,alpha:Number=1,color:uint=0x333333):void {
var mark:Shape = new Shape()
mark.scaleX=0.5
mark.scaleY=0.5
mark.graphics.beginFill(color,alpha)
mark.graphics.moveTo(-10,-4)
mark.graphics.curveTo(-10,-15,-4,-10)
mark.graphics.lineTo(-1,-6)
mark.graphics.lineTo(5,-4)
mark.graphics.curveTo(9,0,5,4)
mark.graphics.lineTo(-1,6)
mark.graphics.lineTo(-4,10)
mark.graphics.curveTo(-10,15,-10,4)
mark.graphics.endFill()
mark.graphics.beginFill(color,alpha)
mark.graphics.drawEllipse(3-5,-10-4,10,8)
mark.graphics.drawEllipse(12-5,-4.5-4,10,8)
mark.graphics.drawEllipse(12-5, 4.5-4,10,8)
mark.graphics.drawEllipse(3-5, 10-4,10,8)
mark.graphics.endFill()
progressVar.addChild(mark)
mark.x=id*19+10
mark.y=id%2*10
}
}
}
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.media.Sound;
class Keyboard extends Sprite {
public static const MAKE_SOUND_END:String = 'make_sound_end';
private var id:uint=0
private var source:Sound;
private var sound:SoundPitchShift;
public var color:uint=0
public function Keyboard(_id:uint,_source:Sound) {
source=_source
id=_id
var i:uint
var n:Array=new Array( 0, 35, 50, 85,100,150,185,200,235,250,285,300,350,385,400,435,450,500,535,550,585,600,635,650)
var m:Array=new Array( 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0)
var result:Sprite = new Sprite();
graphics.lineStyle(1,0x000000,1,true)
if (m[id]) {
graphics.beginFill(0x000000,1)
graphics.drawRect(0,0,30,100)
x=n[id]
} else {
graphics.beginFill(0xFFFFFF,1)
graphics.drawRect(0,0,50,200)
x=n[id]
}
color=m[id]
addEventListener(MouseEvent.MOUSE_DOWN,onDown)
}
private function onDown(e:MouseEvent):void {
trace('onDown')
sound.play();
}
public function makeSound():void {
//周波数(1=基準ラ、0.5=位置オクターブ下がる、2=1オクターブ上がる)
var k:Array=new Array(-12,-11,-10,-9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
var key:Number=k[id]
sound = new SoundPitchShift(source);
sound.stereo=false//true
sound.fftFrameSize = 2048/2
sound.osamp=3
if (key) {
sound.pitch=1*Math.pow(2,key/12)
} else if (key < 0) {
sound.pitch=1/Math.pow(2,Math.abs(key)/12)
} else if (!key) {
sound.pitch=1.01
}
trace(sound.pitch)
sound.makeSound();
this.dispatchEvent(new Event(MAKE_SOUND_END));
}
}
import flash.events.Event;
import flash.events.SampleDataEvent;
import flash.media.Sound;
import flash.net.URLRequest;
import flash.utils.ByteArray;
import flash.utils.getTimer;
class SoundPitchShift extends Sprite{
public var mp3: Sound;
private var _sound: Sound;
private var _target: ByteArray;
private var _soundBuffer: ByteArray;
private var _leftChannel:Vector.<Number> = new Vector.<Number>(8192/2);
private var _rightChannel:Vector.<Number> = new Vector.<Number>(8192/2);
private var _leftPitchShifter:PitchShifter;
private var _rightPitchShifter:PitchShifter;
public var pitch: Number = 1.0;
public function SoundPitchShift(source:Sound)
{
_target = new ByteArray();
mp3 = source;
}
public function makeSound():void {
//変調したsoundを作成・保存
_soundBuffer=onSampleDataAll()
}
public function play(start:Number=0,loop:uint=0):void {
//_constructShifters();
trace('音Start')
var _target:ByteArray=_soundBuffer
var _position:Number=0;
_sound = new Sound();
_sound.addEventListener( SampleDataEvent.SAMPLE_DATA, onSampleDataRequest);
_sound.play(start,loop);
function onSampleDataRequest(e:SampleDataEvent):void {
var data: ByteArray = e.data;
_target.position=_position
var read:int=8192/2
if (0>_target.length/8-(_target.position/8+read)) {
read=_target.length/8-(_target.position/8)
}
_position+=read*8
for( var i:int = 0; i < read; ++i )
{
data.writeFloat(_target.readFloat()*0.5);
data.writeFloat(_target.readFloat()*0.5);
}
}
}
private var _fftFrameSize:int = 1024;
public function get fftFrameSize():int { return _fftFrameSize }
public function set fftFrameSize(v:int):void
{
if (v != _fftFrameSize)
{
_fftFrameSize = v;
_constructShifters();
}
}
private var _osamp:int = 4;
public function get osamp():int { return _osamp }
public function set osamp(v:int):void
{
if (v != _osamp)
{
_osamp = v;
_constructShifters();
}
}
private var _sampleRate:int = 44100;
public function get sampleRate():int { return _sampleRate }
public function set sampleRate(v:int):void
{
if (v != _sampleRate)
{
_sampleRate = v;
_constructShifters();
}
}
private var _stereo:Boolean = false;
public function get stereo():Boolean { return _stereo }
public function set stereo(v:Boolean):void
{
if (_stereo = v)
_rightPitchShifter = new PitchShifter( _fftFrameSize, _osamp, _sampleRate );
}
public var settings:String;
private function _constructShifters():void
{
settings = (_stereo?"stereo-":"mono-") + _fftFrameSize + "-" + _osamp + "-" + _sampleRate;
_cpuFIFO = new Vector.<Number>;
_leftPitchShifter = new PitchShifter( _fftFrameSize, _osamp, _sampleRate );
if ( _stereo )
_rightPitchShifter = new PitchShifter( _fftFrameSize, _osamp, _sampleRate );
}
public var doPitch:Boolean;
public var doStereo:Boolean;
private var _maxCPUPerSetting:Object = { }
private var _cpuFIFO:Vector.<Number>;
private var _cpu:Number = 0.0;
public function get cpu():Number { return _cpu }
public function onSampleDataAll():ByteArray {
var s:int = getTimer();
//-- SHORTCUT
var data: ByteArray = new ByteArray();
//-- REUSE INSTEAD OF RECREATION
_target.position = 0;
var read: int = mp3.extract( _target, mp3.length / 1000 * 44100,0);
_target.position = 0;
trace(read,mp3.length)
var currentMax:Number = _maxCPUPerSetting[settings];
if (isNaN(currentMax)) currentMax = 0.0;
doPitch = currentMax < 1.5;
doStereo = currentMax < 1.0;
for( var i:int = 0; i < read; ++i )
{
if (doStereo)
{
_leftChannel[i] = _target.readFloat()*0.5;
_rightChannel[i] = _target.readFloat()*0.5;
}
else
{
//-- AVG LEFT AND RIGHT CHANNELS
_leftChannel[i] = .5 * ( _target.readFloat() + _target.readFloat() ) * 0.5;
}
}
if ( doPitch ) {
_leftPitchShifter.pitchShift( pitch, read, _leftChannel );
if (_stereo && doStereo) {
_rightPitchShifter.pitchShift( pitch, read, _rightChannel );
}
}
for( i = 0 ; i < read ; ++i )
{
data.writeFloat( _leftChannel[i] );
data.writeFloat( _stereo && doStereo ? _rightChannel[i] : _leftChannel[i] );
}
_cpu = 0.0;
_cpuFIFO.unshift( (getTimer() - s) * _sampleRate * 1.220703125E-7 );
var l:int = _cpuFIFO.length;
if(l>6) _cpuFIFO.splice( --l, 1 );
for ( i = 0 ; i < l ; ++i )
_cpu += _cpuFIFO[i];
_cpu /= Number(l);
if ( currentMax < _cpu )
_maxCPUPerSetting[settings] = _cpu;
return data;
}
}
/****************************************************************************
*
* NAME: PitchShifter.as
* VERSION: 1.0
* HOME URL: http://iq12.com/
* KNOWN BUGS: none
*
* SYNOPSIS: Routine for doing pitch shifting while maintaining
* duration using the Short Time Fourier Transform.
*
* DESCRIPTION: The routine takes a pitchShift factor value which is between 0.5
* (one octave down) and 2. (one octave up). A value of exactly 1 does not change
* the pitch. numSampsToProcess tells the routine how many samples in indata[0...
* numSampsToProcess-1] should be pitch shifted and moved to outdata[0 ...
* numSampsToProcess-1]. The two buffers can be identical (ie. it can process the
* data in-place). fftFrameSize defines the FFT frame size used for the
* processing. Typical values are 1024, 2048 and 4096. It may be any value <=
* MAX_FRAME_LENGTH but it MUST be a power of 2. osamp is the STFT
* oversampling factor which also determines the overlap between adjacent STFT
* frames. It should at least be 4 for moderate scaling ratios. A value of 32 is
* recommended for best quality. sampleRate takes the sample rate for the signal
* in unit Hz, ie. 44100 for 44.1 kHz audio. The data passed to the routine in
* indata[] should be in the range [-1.0, 1.0), which is also the output range
* for the data, make sure you scale the data accordingly (for 16bit signed integers
* you would have to divide (and multiply) by 32768).
*
* COPYRIGHT 1999-2006 Stephan M. Bernsee <smb [AT] dspdimension [DOT] com>
*
* The Wide Open License (WOL)
*
* Permission to use, copy, modify, distribute and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice and this license appear in all source copies.
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
* ANY KIND. See http://www.dspguru.com/wol.htm for more information.
*
*****************************************************************************/
/****************************************************************************
*
* This code was converted to AS3/FP10 by Arnaud Gatouillat <fu [AT] iq12 [DOT] com>
* from C# code by Michael Knight ( madmik3 at gmail dot com. )
* http://sites.google.com/site/mikescoderama/
*
*****************************************************************************/
/****************************************************************************
*
* The functions `realft' and `four1' are based on those in Press, W.H., et al.,
* Numerical Recipes in C: the Art of Scientific Computing (Cambridge Univ. Press,
* 1989; 2nd ed., 1992).
*
*****************************************************************************/
class PitchShifter
{
private var gInFIFO :Vector.<Number>;
private var gOutFIFO :Vector.<Number>;
private var gFFTworksp :Vector.<Number>;
private var gLastPhase :Vector.<Number>;
private var gSumPhase :Vector.<Number>;
private var gOutputAccum:Vector.<Number>;
private var gAnaFreq :Vector.<Number>;
private var gAnaMagn :Vector.<Number>;
private var gSynFreq :Vector.<Number>;
private var gSynMagn :Vector.<Number>;
private var freqPerBin:Number, expct:Number;
private var gRover:int, inFifoLatency:int, stepSize:int, fftFrameSize2:int;
private var fftFrameSize:int, osamp:int, sampleRate:Number;
/* pre-computed values for speed */
private var windowValues :Vector.<Number>;
private var windowValuesFactored:Vector.<Number>;
private var invPI:Number, invFftFrameSizePI2:Number, osampPI2:Number, invOsampPI2FreqBin:Number;
private var PI:Number = Math.PI
private var TWOPI:Number = 2 * Math.PI
public function PitchShifter(fftFrameSize:int, osamp:int, sampleRate:Number)
{
this.fftFrameSize = fftFrameSize;
this.osamp = osamp;
this.sampleRate = sampleRate;
gInFIFO = new Vector.<Number>(fftFrameSize);
gOutFIFO = new Vector.<Number>(fftFrameSize, true);
gFFTworksp = new Vector.<Number>(2 * fftFrameSize + 2, true);
gLastPhase = new Vector.<Number>(fftFrameSize / 2 + 1, true);
gSumPhase = new Vector.<Number>(fftFrameSize / 2 + 1, true);
gOutputAccum = new Vector.<Number>(2 * fftFrameSize, true);
gAnaFreq = new Vector.<Number>(fftFrameSize, true);
gAnaMagn = new Vector.<Number>(fftFrameSize, true);
gSynFreq = new Vector.<Number>(fftFrameSize, true);
gSynMagn = new Vector.<Number>(fftFrameSize, true);
/* set up some handy variables */
fftFrameSize2= fftFrameSize / 2;
stepSize = fftFrameSize / osamp;
freqPerBin = sampleRate / Number(fftFrameSize);
expct = 2.0 * PI * Number(stepSize) / Number(fftFrameSize);
inFifoLatency = fftFrameSize - stepSize;
invPI = 1 / PI;
invFftFrameSizePI2 = PI * 2 / fftFrameSize;
osampPI2 = osamp / ( 2 * PI );
invOsampPI2FreqBin = 1 / ( freqPerBin * osampPI2);
windowValues = new Vector.<Number>(fftFrameSize);
windowValuesFactored = new Vector.<Number>(fftFrameSize);
var invFftFrameSize2:Number = 2.0 / (fftFrameSize2 * osamp);
for (var k:int = 0, t:Number = 0.0; k < fftFrameSize; ++k, t += invFftFrameSizePI2)
{
var window: Number = -.5 * Math.cos(t) + .5;
windowValues[k] = window;
windowValuesFactored[k] = window * invFftFrameSize2;
}
}
public function pitchShift(pitchShift:Number, numSampsToProcess:int, indata:Vector.<Number>):void
{
var magn:Number, phase:Number, tmp:Number, window:Number, real:Number, imag:Number, t:Number;
var i:int, k:int, qpd:int, index:int, n:int;
var outdata:Vector.<Number> = indata;
if (gRover == 0) gRover = inFifoLatency;
/* main processing loop */
for (i = 0; i < numSampsToProcess; ++i)
{
/* As long as we have not yet collected enough data just read in */
gInFIFO[gRover] = indata[i];
outdata[i] = gOutFIFO[gRover - inFifoLatency];
++gRover;
/* now we have enough data for processing */
if (gRover >= fftFrameSize)
{
gRover = inFifoLatency;
/* do windowing and re,im interleave */
for (k = 0, n = 1; k < fftFrameSize; ++k, ++n)
{
gFFTworksp[n] = gInFIFO[k] * windowValues[k];
gFFTworksp[++n] = 0.0;
}
/* ***************** ANALYSIS ******************* */
/* do transform */
realft(gFFTworksp, fftFrameSize, -1);
/* this is the analysis step */
for (k = 0; k <= fftFrameSize2; ++k)
{
/* de-interlace FFT buffer */
real = gFFTworksp[n = 1 + (k << 1)];
imag = gFFTworksp[n + 1];
/* compute magnitude and phase */
magn = 2.0 * Math.sqrt(real * real + imag * imag);
phase = Math.atan2(imag, real);
/* compute phase difference */
tmp = phase - gLastPhase[k];
gLastPhase[k] = phase;
/* subtract expected phase difference */
tmp -= k * expct;
/* map delta phase into +/- Pi interval */
qpd = int(tmp * invPI);
if (qpd >= 0) qpd += qpd & 1;
else qpd -= qpd & 1;
tmp -= PI * Number(qpd);
/* get deviation from bin frequency from the +/- Pi interval */
tmp *= osampPI2;
/* compute the k-th partials' true frequency */
tmp = (k + tmp) * freqPerBin;
/* store magnitude and true frequency in analysis arrays */
gAnaMagn[k] = magn;
gAnaFreq[k] = tmp;
}
/* ***************** PROCESSING ******************* */
/* this does the actual pitch shifting */
for (var zero:int = 0; zero < fftFrameSize; ++zero)
{
gSynMagn[zero] = 0.0;
gSynFreq[zero] = 0.0;
}
for (k = 0, n = pitchShift > 1.0 ? int(fftFrameSize2 / pitchShift) : fftFrameSize2; k <= n; ++k)
{
index = int(k * pitchShift);
gSynMagn[index] += gAnaMagn[k];
gSynFreq[index] = gAnaFreq[k] * pitchShift;
}
/* ***************** SYNTHESIS ******************* */
/* this is the synthesis step */
for (k = 0; k <= fftFrameSize2; ++k)
{
/* get magnitude and true frequency from synthesis arrays */
magn = gSynMagn[k];
/* subtract bin mid frequency */
/* get bin deviation from freq deviation */
/* take osamp into account */
/* add the overlap phase advance back in */
/* accumulate delta phase to get bin phase */
phase = (gSumPhase[k] += (gSynFreq[k] - Number(k) * freqPerBin) * invOsampPI2FreqBin + Number(k) * expct);
/* get real and imag part and re-interleave */
gFFTworksp[n = 1 + (k << 1)] = magn * Math.cos(phase);
gFFTworksp[n + 1] = magn * Math.sin(phase);
}
/* zero negative frequencies */
for (k = fftFrameSize + 3, n = 1 + (fftFrameSize << 1); k < n; ++k)
{
gFFTworksp[k] = 0.0;
}
/* do inverse transform */
realft(gFFTworksp, fftFrameSize, 1);
/* do windowing and add to output accumulator */
for (k = 0, n = 1; k < fftFrameSize; ++k, ++n, ++n)
{
gOutputAccum[k] += windowValuesFactored[k] * gFFTworksp[n];
}
for (k = 0; k < stepSize; ++k)
{
gOutFIFO[k] = gOutputAccum[k];
}
//memmove(gOutputAccum, gOutputAccum + stepSize, fftFrameSize * sizeof(Number));
/* shift accumulator */
/* move input FIFO */
for (k = 0, n = stepSize; k < inFifoLatency; ++k, ++n)
{
gOutputAccum[k] = gOutputAccum[n];
gInFIFO[k] = gInFIFO[n];
}
for ( ; k < fftFrameSize; ++k, ++n)
{
gOutputAccum[k] = gOutputAccum[n];
}
}
}
}
private function realft( data:Vector.<Number>, n:int, isign:int ):void
{
var i:int, i1:int, i2:int, i3:int, i4:int, n2p3:int;
var c1:Number = 0.5, c2:Number, h1r:Number, h1i:Number, h2r:Number, h2i:Number;
var wr:Number, wi:Number, wpr:Number, wpi:Number, wtemp:Number, theta:Number;
theta = PI/n;
if (isign == 1)
{
c2 = -0.5;
four1(data, n, 1);
}
else
{
c2 = 0.5;
theta = -theta;
}
wtemp = Math.sin(0.5 * theta);
wpr = -2.0 * wtemp * wtemp;
wpi = Math.sin(theta);
wr = 1.0 + wpr;
wi = wpi;
n2p3 = 2 * n + 3;
for (i = 2; i <= n / 2; ++i)
{
i4 = 1 + (i3 = n2p3 - (i2 = 1 + ( i1 = i + i - 1)));
h1r = c1 * (data[i1] + data[i3]);
h1i = c1 * (data[i2] - data[i4]);
h2r = -c2 * (data[i2] + data[i4]);
h2i = c2 * (data[i1] - data[i3]);
data[i1] = h1r + wr * h2r - wi * h2i;
data[i2] = h1i + wr * h2i + wi * h2r;
data[i3] = h1r - wr * h2r + wi * h2i;
data[i4] = -h1i + wr * h2i + wi * h2r;
wr = (wtemp = wr) * wpr - wi * wpi + wr;
wi = wi * wpr + wtemp * wpi + wi;
}
if (isign == 1)
{
data[1] = (h1r = data[1]) + data[2];
data[2] = h1r - data[2];
}
else
{
data[1] = c1 * ((h1r = data[1]) + data[2]);
data[2] = c1 * (h1r - data[2]);
four1(data, n, -1);
data=data;
}
}
private function four1(data:Vector.<Number>, nn:int, isign:int):void
{
var n:int, mmax:int, m:int, j:int, istep:int, i:int;
var wtemp:Number, wr:Number, wpr:Number, wpi:Number, wi:Number, theta:Number;
var tempr:Number, tempi:Number;
var j1:int, i1:int;
n = nn << 1;
j = 1;
for (i = 1; i < n; i += 2)
{
if (j > i)
{
j1 = j + 1;
i1 = i + 1;
tempr = data[j]; data[j] = data[i]; data[i] = tempr;
tempr = data[j1]; data[j1] = data[i1]; data[i1] = tempr;
}
m = n >> 1;
while (m >= 2 && j > m)
{
j -= m;
m >>= 1;
}
j += m;
}
mmax = 2;
while (n > mmax)
{
istep = 2 * mmax;
theta = TWOPI / (isign * mmax);
wtemp = Math.sin(0.5 * theta);
wpr = -2.0 * wtemp * wtemp;
wpi = Math.sin(theta);
wr = 1.0;
wi = 0.0;
for (m = 1; m < mmax; m += 2)
{
for (i = m; i <= n; i += istep)
{
i1 = i +1;
j1 = 1+ (j = i + mmax);
tempr = wr*data[j] - wi*data[j1];
tempi = wr*data[j1] + wi*data[j];
data[j] = data[i] - tempr;
data[j1] = data[i1] - tempi;
data[i] += tempr;
data[i1] += tempi;
}
wr = (wtemp = wr) * wpr - wi * wpi + wr;
wi = wi * wpr + wtemp * wpi + wi;
}
mmax = istep;
}
}
}