Swift 固体节拍器系统 [英] Swift Solid Metronome System

查看:92
本文介绍了Swift 固体节拍器系统的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试构建一个可靠的实体系统,以便使用 SWIFT 在我的应用中构建节拍器.

I am trying to build a reliable solid system to build a metronome in my app using SWIFT.

到目前为止,我已经使用 NSTimer 构建了一个看似可靠的系统.我现在唯一遇到的问题是计时器开始时,前 2 次点击是关闭时间,但随后它进入了一个稳定的时间范围.

I Have built what seems to be a solid system using NSTimer so far.. The only issue I am having right now is when the timer starts the first 2 clicks are off time but then it catches into a solid timeframe.

现在,在我所有的研究之后,我看到人们提到您应该使用其他不依赖 NSTimer 的音频工具.或者,如果您选择使用 NSTimer,那么它应该在自己的线程上.现在我看到很多人对此感到困惑,包括我自己,我很想深入了解节拍器业务的底部,解决这个问题,并与所有苦苦挣扎的人分享.

Now after all my research I have seen people mention you should use other Audio tools not relying on NSTimer.. Or if you choose use NSTimer then it should be on its own thread. Now I see many confused by this Including myself and I would love to get down to the bottom of this Metronome business and get this solved and share it with all those who are struggling.

更新

因此,在我上次收到反馈后,我已经实施并清理了这一点.在这一点上,这是我的代码的结构.它的播放.但是一开始我仍然获得了 2 次快速点击,然后就稳定下来了.

So I have implemented and cleaned up at this point after the feedback I had last recieved. At this point here is how my code is structured. Its playing back. But I am still getting 2 fast clicks in the beginning and then it settles in.

我为我对此一无所知而道歉.我希望我走在正确的道路上.

I apologize on my ignorance for this one. I hope I am on the right path.

我目前也在设计另一种方法的原型.我有一个非常小的音频文件,单击一下,结尾处有死区,持续时间正确,直到特定节奏的循环点.我正在循环这个并且效果很好.但唯一的事情是我没有检测到视觉更新的循环点,所以我有我的基本 NStimer 只是检测正在处理的音频下面的时间间隔,它似乎在整个过程中匹配得很好,没有延迟.但我还是更愿意用这个 NSTimer 来获得这一切.如果您能轻松地发现我的错误,那么在正确的方向上再踢一脚就好了,我相信它很快就会起作用!非常感谢.

I currently am prototyping another method as well. Where I have a very small audio file with one click and dead space at the end of it with the correct duration until for a loop point for specific tempos. I am looping this back and works very well. But the only thing Is I dont get to detect the loop points for visual updates so I have my basic NStimer just detecting the timing intervals underneath the audio being processed and it seems to matchup very well throughout and no delay. But I still would rather get it all with this NSTimer. If you can easily spot my error would be great for one more kick in the right direction and I am sure it can work soon! Thanks so much.

    //VARIABLES 
    //AUDIO
    var clickPlayer:AVAudioPlayer = AVAudioPlayer()
    let soundFileClick = NSBundle.mainBundle().pathForResource("metronomeClick", ofType: ".mp3")

    //TIMERS
    var metroTimer = NSTimer()
    var nextTimer = NSTimer()

    var previousClick = CFAbsoluteTimeGetCurrent()    //When Metro Starts Last Click


    //Metro Features
    var isOn            = false
    var bpm             = 60.0     //Tempo Used for beeps, calculated into time value
    var barNoteValue    = 4        //How Many Notes Per Bar (Set To Amount Of Hits Per Pattern)
    var noteInBar       = 0        //What Note You Are On In Bar


    //********* FUNCTIONS ***********

func startMetro()
{
     MetronomeCount()

    barNoteValue    = 4         // How Many Notes Per Bar (Set To Amount Of Hits Per Pattern)
    noteInBar       = 0         // What Note You Are On In Bar
    isOn            = true      //

        }

        //Main Metro Pulse Timer
        func MetronomeCount()
        {
            previousClick = CFAbsoluteTimeGetCurrent()

        metroTimer = NSTimer.scheduledTimerWithTimeInterval(60.0 / bpm, target: self, selector: Selector ("MetroClick"), userInfo: nil, repeats: true)

        nextTimer = NSTimer(timeInterval: (60.0/Double(bpm)) * 0.01, target: self, selector: "tick:", userInfo: ["bpm":bpm], repeats: true)
    }


    func MetroClick()
    {
        tick(nextTimer)
    }

    func tick(timer:NSTimer)
    {
        let elapsedTime:CFAbsoluteTime = CFAbsoluteTimeGetCurrent() - previousClick
        let targetTime:Double = 60/timer.userInfo!.objectForKey("bpm")!.doubleValue!
        if (elapsedTime > targetTime) || (abs(elapsedTime - targetTime) < 0.003)
        {
            previousClick = CFAbsoluteTimeGetCurrent()

            //Play the click here
            if noteInBar == barNoteValue
            {
                clickPlayer.play()    //Play Sound
                noteInBar = 1
            }
            else//If We Are Still On Same Bar
            {
                clickPlayer.play()    //Play Sound
                noteInBar++             //Increase Note Value
            }

            countLabel.text = String(noteInBar)     //Update UI Display To Show Note We Are At
        }

    }

推荐答案

纯粹使用 NSTimer 构建的节拍器不会非常准确,正如 Apple 在其文档中所解释的那样.

A metronome built purely with NSTimer will not be very accurate, as Apple explains in their documentation.

由于典型运行循环管理的各种输入源,计时器时间间隔的有效分辨率被限制在 50-100 毫秒的数量级.如果计时器的触发时间发生在长时间调用期间或运行循环处于未监视计时器的模式时,则在运行循环下一次检查计时器之前,计时器不会触发.

Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds. If a timer’s firing time occurs during a long callout or while the run loop is in a mode that is not monitoring the timer, the timer does not fire until the next time the run loop checks the timer.

我建议使用 NSTimer,它按每个所需滴答的数量级触发 50 次(例如,如果您想要每分钟 60 次滴答,则可以使用 NSTimeInterval 大约为 1/50 秒.

I would suggest using an NSTimer that fires on the order of 50 times per desired tick (for example, if you would like a 60 ticks per minute, you would have the NSTimeInterval to be about 1/50 of a second.

然后您应该存储一个 CFAbsoluteTime 来存储最后一个滴答"时间,并将其与当前时间进行比较.如果当前时间和最后一个滴答"时间之间的差异的绝对值小于某个容差(我会将其设为每个间隔滴答数的 4 倍,例如,如果您选择 1/50 秒每个 NSTimer 触发,您应该应用大约 4/50 秒的容差),您可以播放滴答声".

You should then store a CFAbsoluteTime which stores the "last tick" time, and compare it to the current time. If the absolute value of the difference between the current time and the "last tick" time is less than some tolerance (I would make this about 4 times the number of ticks per interval, for example, if you chose 1/50 of a second per NSTimer fire, you should apply a tolerance of around 4/50 of a second), you can play the "tick."

您可能需要校准公差以达到您想要的准确度,但这个通用概念将使您的节拍器更加准确.

You may need to calibrate the tolerances to get to your desired accuracy, but this general concept will make your metronome a lot more accurate.

以下是有关另一篇 SO 帖子的更多信息.它还包括一些使用我讨论过的理论的代码.我希望这会有所帮助!

Here is some more information on another SO post. It also includes some code that uses the theory I discussed. I hope this helps!

更新您计算公差的方式不正确.在您的计算中,请注意容差与 bpm 的平方成反比.这样做的问题是容差最终将小于计时器每秒触发的次数.看看这张图,看看我的意思.这将在高 BPM 下产生问题.另一个潜在的错误来源是您的上边界条件.你真的不需要检查你的容差上限,因为理论上,到那时计时器应该已经触发了.因此,如果经过的时间大于理论时间,您可以不管它.(例如,如果经过的时间是 0.1 秒,而真实 BPM 的实际时间应该是 0.05 秒,那么无论如何您都应该继续并触发计时器,无论您的容忍度是多少).

Update The way you are calculating your tolerances is incorrect. In your calculations, notice that the tolerance is inversely proportional to the square of the bpm. The problem with this is that the tolerance will eventually be less than the number of times the timer fires per second. Take a look at this graph to see what I mean. This will generate problems at high BPMs. The other potential source of error is your top bounding condition. You really don't need to check an upper limit on your tolerance, because theoretically, the timer should have already fired by then. Therefore, if the elapsed time is greater than the theoretical time, you can fire it regardless. (For example if the elapsed time is 0.1s and and the actual time with the true BPM should be 0.05s, you should go ahead and fire the timer anyways, no matter what your tolerance is).

这是我的计时器滴答"功能,它似乎工作正常.您需要对其进行调整以适应您的需求(使用强拍等),但它在概念上是有效的.

Here is my timer "tick" function, which seems to work fine. You need to tweak it to fit your needs (with the downbeats, etc.) but it works in concept.

func tick(timer:NSTimer) {
    let elapsedTime:CFAbsoluteTime = CFAbsoluteTimeGetCurrent() - lastTick
    let targetTime:Double = 60/timer.userInfo!.objectForKey("bpm")!.doubleValue!
    if (elapsedTime > targetTime) || (abs(elapsedTime - targetTime) < 0.003) {
        lastTick = CFAbsoluteTimeGetCurrent()  
        # Play the click here
    }
}

我的计时器初始化如下:nextTimer = NSTimer(timeInterval: (60.0/Double(bpm)) * 0.01, target: self, selector: "tick:", userInfo: ["bpm":bpm],重复:真)

My timer is initialized like so: nextTimer = NSTimer(timeInterval: (60.0/Double(bpm)) * 0.01, target: self, selector: "tick:", userInfo: ["bpm":bpm], repeats: true)

这篇关于Swift 固体节拍器系统的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆