4

再生中のノートを表すグリッド上を移動したい垂直の 1 ピクセル ビューがあります。曲の BPM (ビート/分) に応じて、アニメーションが遅くなったり速くなったりするはずです。メモは隣り合わせに圧縮され、幅は 25px です。

私が今これを行っている方法は、xサンプルが通過するたびにビューを1px右に移動することです:

int bpm = 90;
int interval = (44100 * 60.0f / song.bpm) / 25.0f; // = 1176
// sample rate=44100 multiplied by 60 (seconds) divided by number of pixels

したがって、BPM=90 の場合、1176 サンプルを再生するたびに UIView を 1px 移動します。これはかなりうまく機能しますが、BPM が 180 を超えると問題が発生します。その時点で、再生される音楽のビューが遅れており、BPM が 240 まで上がると、ビューが大きく外れます。

ビューの組み込みのアニメーション機能を使用してこれを行うにはどうすればよいですか?また、上記で投稿した計算に基づいてアニメーションの速度を設定するにはどうすればよいですか? 音楽の再生が始まったらアニメーションを開始したいので、ピクセルごとに手動で移動する必要はありません。

4

3 に答える 3

4

私が正しく理解していれば、あなたがしたいことは次のとおりです。

  1. 静的な背景画像を描画します。
  2. 左から右に移動する線の上に垂直線を描画します。
  3. ラインのx位置をオーディオ再生時間と同期させます。

解決すべき2つの問題があります。

  • 描画性能
  • 音声同期

描画パフォーマンスを正しくするために、考慮すべき2つの実装があります。

  1. Core Animationレイヤーとアニメーションの使用(2よりもはるかに簡単に実装できます)。
  2. OpenGLを使用してビュー全体を描画します(おそらく1よりも少し速く、少し待ち時間が短くなります)。

OpenGLでビューを実装することは、例として投稿するにはコードが多すぎますが、CoreAnimationオプションのコードは次のとおりです。

// In the UIViewController:

- (CALayer *)markerLayer
{
    static NSString *uniqueName = @"com.mydomain.MarkerLayer";
    for (CALayer *layer in self.view.layer.sublayers) {
        if ([layer.name isEqualToString:uniqueName])
            return layer;
    }

    CALayer *markerLayer = [CALayer layer];
    markerLayer.backgroundColor = [UIColor whiteColor].CGColor;
    markerLayer.name = uniqueName;
    markerLayer.anchorPoint = CGPointZero;
    [self.view.layer addSublayer:markerLayer];
    return markerLayer;
}

- (void)startAnimationWithDuration:(NSTimeInterval)duration
{
    [CATransaction begin];
    [CATransaction setDisableActions:YES];

    CALayer *markerLayer = [self markerLayer];

    markerLayer.bounds = (CGRect){.size = {1, self.view.bounds.size.height}};
    markerLayer.position = CGPointZero;
    [CATransaction commit];

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.x"];
    animation.fromValue = @0.0f;
    animation.toValue = [NSNumber numberWithFloat:self.view.bounds.size.width];
    animation.duration = duration;
    [markerLayer addAnimation:animation forKey:@"audioAnimation"];
}

オーディオの同期は、たとえばAVFoundationやCore Audioを使用するなど、再生方法によって大きく異なります。正確な同期を得るには、オーディオエンジンからのある種のコールバックが必要になります。これは、再生が現在どこにあるか、またはいつ開始されたかを通知します。

オーディオを開始する直前に電話をかけるだけで十分かもしれませんがstartAnimationWithDuration:、それはちょっと壊れやすいようで、オーディオフレームワークのレイテンシーに依存します。

于 2012-08-23T17:28:10.443 に答える
1

1つの解決策:図のような背景ビュー(青いボックス)があります。このビューが不透明であることを確認してください。新しいUIViewサブクラスを定義し、それを私がバックグラウンドビューと呼んでいるものの上に配置します(このビューは不透明ではありません)。これをマーカービューと呼びましょう。

マーカービューは、線を描画するためのxオフセットをいつでも知ることができます。これは、rectパラメーターをクリアカラー(alpha = 0)に設定するdrawRectメソッドを提供します。次に、Quartz呼び出しを使用して単一の垂直線を描画します(ブール値がそれを行うように指示した場合)。

コントローラが起動すると、このビューに線を引かないように指示します。起動時に、ブール値をYESに変更し、x座標を指定してから、ビューsetNeedsDisplayInRect:CGRectMake(theXoffset、0、1、heightOfView)];を指定します。

多少の遅延がある場合でも、ビューが最終的に描画するときは、「リアルタイム」で更新しているため、x値は最新である必要があります。

編集:

  • マーカーとは、垂直線を意味します-それは場所を「マーク」しませんか?

  • alpha 0は、(この場合)オーバーレイビューの大部分である「マーカー」ビューがクリアカラーになることを意味します。つまり、各ピクセルがクリア(alpha = 0)になり、その下のビューがブロックされないようになります。このビューによって描画される唯一のピクセルは垂直線です。

  • BOOL、"x"など-ビューの領域プロパティ。もう少し必要かもしれませんが、多くは必要ありません。これらのプロパティは、「drawRect:」が呼び出されたときに何をすべきかをビューに指示します。

  • Quartz(Appleが実際に描画するために使用するテクノロジー)について少し学ぶ必要があります。drawRectでは、現在のCGContextRefを取得し、それを事実上すべての呼び出しで使用して、描画する線の幅を設定し(デフォルトは1)、線の色を設定し、あるポイントに移動し、他のポイントに線を描画しますポイント(これは「パス」になります)、次にパスをストロークします。これは、他のほとんどのソリューションと比較して非常に高速です(ただし、それ自体は高速化できます)。

これがすべて気が遠くなるように思われる場合は、おそらく他の誰かがより簡単な解決策を持っているか、またはあなたは賞金を提供して、誰か(私のような)が実際にあなたのためにすべてをコーディングするかどうかを確認できます。

EDIT2:

これは、インジケーターラインを実行するビューのコードです(見やすくするために赤い線を使用しました。この配列は色を設定します:{1、0、0、R / G / Bスペース)

#import "SlideLine.h"

#define LINE_WIDTH 1.0f

@implementation SlideLine
@synthesize showLine, x;

- (void)drawRect:(CGRect)rect
{
    if(!showLine) return;

    CGRect bounds = self.bounds;

    CGContextRef c = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(c, LINE_WIDTH);

    // draws two slightly transparent lines on either side to soften the look
    for(int i=0; i<3; ++i) {
        CGFloat val[4] = { 1, 0, 0, i==1 ? 1 : 0.5f}; // half alpha for each side line
        CGContextSetStrokeColor(c, val);

        CGFloat xx = x - LINE_WIDTH/2 - 1 + i;
        CGContextMoveToPoint(c, xx, 0);
        CGContextAddLineToPoint(c, xx, bounds.size.height);
        CGContextStrokePath(c);
    }
}

- (void)setShowLine:(BOOL)val
{
    showLine = val;
    [self setNeedsDisplay];
}

@end

速度を変更するためのコントロールなどを使用して、その使用方法を示すXcodeプロジェクトを作成しました。

ここに画像の説明を入力してください

EDIT3:それで、アニメーションを使用するように変更を開始しましたが、残念ながら完全には焼き付けられていません。アイデアは、マーカーライン(幅3ピクセル)を取得して、単一のビュー(幅3ピクセル)にすることです。新しいSlidingLineControllerは、所有者から更新を取得し(オーディオコールバックによって駆動されます)、xがいつどこにあるべきか(つまりレート)をコントローラーに通知します。次に、コントローラーはそのレートを使用してマーカーをアニメーション化し、指定された時間にマーカーが配置されるようにします。更新が行われると、アニメーションレートがわずかに速くなったり遅くなったりするため、絶対位置は常に希望の位置に近くなります。

これはオリジナルよりも優れたテクニックのように見えますが、実際にはオリジナルよりも「途切れ途切れ」に見えます。おそらく最適な解決策は、レイヤー(ビューではなく)を使用してアニメーション化し、開始して再起動するのではなく、移動時にターゲットとレートを更新することです。おそらく、この2番目のプロジェクトでは、最初の手法を使用して、同じレート管理の概念を使用して30fpsのレートで自分自身を再描画することができます。残念ながら、これを追求する時間がなくなったので、これはまもなく期限切れになります。

編集:4さて、私は別の更新を行い、上記のようにコントローラーを使用して30 fpsの速度で再描画しましたが、かなりうまく機能しています-まだ完了していませんが、3番目のスピンでかなりスムーズであることがわかります

于 2012-08-21T01:16:40.843 に答える
0

あなたはしたいかもしれない:

  • 再生位置をサンプルに格納するアトミック int 変数を宣言します
  • NSTimer適切な頻度 (たとえば、画面のリフレッシュ レートよりも低い) で、再生中にメイン スレッドに をインストールします。
  • オーディオをレンダリングするとき、atomic int の位置を適切な量だけ進めます。
  • タイマーが起動したら、オーディオ スレッドによって書き込まれた位置を読み取ります。setNeedsDisplay変更された場合は、曲の位置ビューの呼び出しや再配置など、適切な操作を行って UI を更新してください。

複数のレンダラーとの間でプッシュすることになると、最適化の試みが裏目に出る可能性があるため、これをお勧めします。

于 2012-08-29T04:46:18.693 に答える