2018年4月15日日曜日

カモメに帰る日(考えたよ)

唐突ではありますが、僕のテレキャスターの3弦がどうもよろしくないのです。

オクターブチューニングをきっちり合わせても、ちゃんとピッチが合わないのです。ひょっとしてフレット音痴?と思ったりもするのですが、他の弦は大丈夫ですし、新品で購入してからほとんど弾いてないのでフレットも痛んでません。

まぁ、そんなものかなぁ、と思いながら「そこそこな感じ」でチューニングしていたのですが、チューナーアプリを作ってる身としては、

「それじゃダメじゃん(©春風亭昇太)」

なわけです。そこで、この音痴な3弦についてちょっと考えたので、それを。

症状としては、
  • オクターブチューニングをきっちり合わせる。
  • 当然開放弦はあっている。
  • でも、12フレット以外(例えば5フレットとか7フレット)を押さえると、ちょっと高い。

ここから考えられるのは、開放弦が本来のピッチより低くなっている、つまり開放弦の弦長が長いってことなのでは?となるのです。

そうすると、犯人はヤス原因はナットにあるということになりそうです。

文章で書くとわかりにくいですが、図にするとこうです。



ナットは開放弦の支点になりますが、正確にはナットの指板側の端(いわゆる0フレット)が支点にならないといけません。でも、支点がわずかにナットの内側にずれていたとしたらどうでしょうか。振動している弦が、ナットの溝の中で踊っている感じです。

これで1フレットの幅だけ正確な幅より少し広いという状態ができます。そんなギターは、たとえオクターブチューニングをしっかりあわせても、開放弦と12フレットを押さえた時にだけ正確な音程になるだけで、他のフレットを押さえたときには少しずつ音が外れてしまいます。

まさに上で示した症状そのものです。

では、なぜ弦の支点がナットの中にずれてしまうのでしょうか。

理由はいくつか考えられます。
  1. ナットの溝が広すぎる
  2. ナットの溝の角度が甘い
  3. ナットの溝の形状が悪い
  4. 弦をナットに押しつける力が弱い
  5. ビグスビーの呪い

1のナットの溝の幅は、そもそも弦に対して少し余裕を持たせて切ってあるものですし、支点のためにキツキツにするものではありません。そんなことをしたら、弦のゲージを変更するたびに、ナットを交換しなければなりません。

では、2はどうでしょうか。角度①がよほど浅いか逆になってない限りは問題なさそうです。もちろん、本来なら角度②に合わせて、きっちり切られている必要がありますけどね。


次に3ですが、このギターは買ってからほとんど弾いていないので、ナットはほぼ新品です。仮にフェンダーの仕事が多少の雑だったとしても、そんなにひどくはないでしょう。

ただ、これが原因だとすると、ちょっとやっかいです。溝の切り直しが必要ですし、その結果弦高が足りなくなるようなことになれば、ナットの交換ということになります。

極端に書くとこう。


そして4です。

角度②が浅いと、弦をナットに押さえつける力が弱いので、弦が滑ってナット上で振動してしまいます。いくら角度①が精密に切られていても効果半減です。さらにナットの溝の形状が少しでも悪ければ、大きく影響がでます。

逆に言えば、弦を押さえつける力がある程度強ければ、多少ナットの溝の角度や形が悪くても何とかなるといえます。

まさに、力は正義(笑)

この問題はギブソン系の(というか普通の)ギターのようにヘッドに角度が付いているものでは起きにくいですが、フェンダー系のヘッドで、特にストリングガイドの付いていない3弦、4弦で起きやすいと思います。

そこで、3弦(4弦もですが)にもストリングガイドを付けて、力業で解決できるのではないかというわけです。この仮説が正しいかどうか確かめようと、、、

今回はここまで。


あ、ちなみに5ですが、このテレキャスターにはあのアームユニットは付けてないので大丈夫です(笑)。

2018年4月1日日曜日

使ってみた(App Extension編) おまけ

「使ってみた(App Extension編) その5」からのつづきです。AppExtensionの話題からどんどん離れていきますが、今回はおまけです。


繰り返しになりますが、AppExtensionと収容アプリケーションは別アプリ扱いです。

そのため、実際のユースケースでは、AppExtensionを使った後ですぐに収容アプリケーションを起動するとは限りません。AppExtensionでのコンテンツの保存を繰り返した後に、収容アプリケーションを起動するというケースも普通にあるでしょう。

そこで、もうひと工夫して、AppExtensionでUserDefaultに保存するときに、Contents型の配列でため込んで、収容アプリケーションで取得するときにまとめて取得するようにします。

好都合なことに、Codableの配列はCodableなのでそのままエンコード、デコードできます。

UserDefaultは上書き保存ですから、AppExtensionでデータを保存するときには、いったんデータを取得してから、その配列に新たにデータを追加してから保存し直します。

これでAppExtensionでどんどん追加保存できます。
    /// コンテンツをApp Groupのユーザーデフォルトに保存
    ///
    /// - Parameter content:
    private func saveContent(content: String) {
        guard let userDefaults = UserDefaults.init(suiteName: "group.jp.blowbend.ios.test.AwesomeApp") else {
            print("userdefault: app group suite name is nil")
            return
        }
        
        //保存済みのデータの取得
        var contents: [Contents] = []
        if let object = userDefaults.object(forKey: "user default key content") {
            if let data: Data = object as? Data {
                do {
                    contents = try JSONDecoder().decode([Contents].self, from: data)
                } catch {
                    print("ActionViewController: JSONDecoder decode error")
                }
            }
        }
        
        let c = Contents(key: 123, text: content, date: Date())
        contents.append(c) //新たにデータを追加
        do {
            let data = try JSONEncoder().encode(contents) //Data型に変換
            userDefaults.set(data, forKey: "user default key content")
            userDefaults.synchronize()
        } catch {
            print("userdefault: contents encode error!!")
        }
    }

収容アプリケーションでデータを取得するときは、単純に配列で受け取ります。貯まっていたデータを受け取ったら、removeObjectでデータを削除して空にします。
    /// App Groupのユーザーデフォルトからコンテンツを取得
    private func getContentFromAppGroup() {
        guard let userDefaults = UserDefaults.init(suiteName: "group.jp.blowbend.ios.test.AwesomeApp") else {
            print("userdefault: app group suite name is nil")
            return
        }
        guard let content = userDefaults.object(forKey: "user default key content") ?? nil else {
            print("userdefault: no content")
            return
        }
        guard let data = content as? Data else {
            print("userdefault: no data")
            return
        }
        
        do {
            let contents = try JSONDecoder().decode([Contents].self, from: data) //Content型の配列で取得
            for c in contents {
                print("c.key =\(c.key)")
                print("c.text=\(c.text)")
                print("c.date=\(c.date)")
            }
        } catch {
            print("userdefault: JSONDecoder decode error")
        }

        userDefaults.removeObject(forKey: "user default key content") //すべてのデータの削除
    }


どうでしょうか。これでAppExtensionの呼び出しごとに、(実は動いていない)収容アプリケーションにデータを送っているかのように見せられますね。このあたりは、その処理に合わせて色々工夫のしどころかもしれません。

以上で、使ってみた(App Extension編)は終わりです。おつかれさまでした。


こんな風にAppExtensionを使ったアプリをリリースしました。よろしくね。


「おまけのオマケ」
App Extensionを含むアプリをiTunes Storeにリリースするときは、ビルド番号を収容アプリケーションとApp Extensionとで揃える必要があります。

もし違うままアップロードすると、

WARNING ITMS-90473: "CFBundleVersion Mismatch. The CFBundleVersion value '2' of extension 'AwsomeApp.app/PlugIns/AwsomeAppShare.appex' does not match the CFBundleVersion value '1' of its containing iOS application 'AwsomeApp.app'."

というワーニングが出ます。ワーニングなので実害はないのかもしれませんが、まぁ、不用なトラブルは避けた方がいいですよね。お気を付けください。


♪♪♪


こちらもどうぞ。
使ってみた(App Extension編) その1
使ってみた(App Extension編) その2
使ってみた(App Extension編) その3
使ってみた(App Extension編) その4
使ってみた(App Extension編) その5

iOS 11 Programming
  • 著者:堤 修一,吉田 悠一,池田 翔,坂田 晃一,加藤 尋樹,川邉 雄介,岸川克己,所 友太,永野 哲久,加藤 寛人,
  • 発行日:2017年11月16日
  • 対応フォーマット:製本版,PDF
  • PEAKSで購入する