おえかきTips

ご無沙汰してます。∠KOH(かっこう)です。CBで皆伝になりました。

訳あって実家暮らしなのですが雪で死にそうです。クソ寒いんじゃボケ。

 

今回は趣味で12年近くやってるおえかきについてまとめてみます。

IIDXの姫留ちゃん*1を描く過程を大雑把に紹介する形式で進めます。

 

ほぼほぼ二次元の美少女しか描いてきていないのでそれ以外のことは書けません。悪しからず。

ノウハウという程ではないですが絵を描く際に知っておくべきペイントツールの機能や自分が絵を描くときにやってる小技とかそういうフワッとしたものを紹介するつもりです。備忘録も兼ねてます。

内容はおえかき初心者だけどちょっと上手く見せられるようになりたい! みたいな人向けです。僕より基礎がしっかりしてる人(絵を普段から描いてる人のほとんど)にはツッコミどころ満載の楽しい記事になるのでそれはそれで読んでください。てか全員読め。

(※この記事はB4UTアドベントカレンダー2018に向けて書いたものです。音ゲー要素は希薄です)

 

-------------------------

目次

 

-------------------------

1.自己紹介

 

絵を描くときはArclo(あるくろ)の名義を使うことが多いです。

おえかき歴12年とは言いつつもダラダラ描いてきただけなので残念ながらめちゃ上手い訳じゃないです。

 

最近B4UT関係で描いたのはχ-dempa from Europarianのジャケットです↓ 

f:id:kackoh:20181211221116p:plain

ネミミミミズさんの大変すばらしい曲です

 

使用ソフト:CLIP STUDIO PAINT PRO (クリスタ)

ペンタブレット:Cintiq 13HD (液タブ、画面にそのまま描けるアレ)

 

僕の描いた絵に関しては、

twitter

twitter.com

か、pixiv↓

pixiv.me

にて公開しています(R-18注意)。よかったらフォロー等よろしくお願いします(宣伝)

 

 -------------------------

2.絵を描き始めるにあたって

 

 初めてだけど試しにおえかきしてみます、という方はとりあえず適当なノートとシャープペンシルで描いてみましょう。絵を描くだけならお金はほぼかかりません。嬉しいことに。

ちょっと頑張ってみようかな、と思ってるそこのアナタ。ペンタブレットは安いものならクリスタPROの2年ライセンス付きで1万円切ります。買いましょう。

すでに絵を描いてるお前は液タブを買えば僕の5000兆倍上手くなれるぞ。

 

一応なのですが、画力誤魔化し術を使うなら断然デジタルでのおえかきを勧めます。アナログでは出来ないことが出来るので小技の幅が違います。

 

この記事は基本的にデジタルで描くことを想定していますがある程度はアナログでも使える話だと思います。

 

 -------------------------

3.初めにアタリをとる

 

紙とペンを用意して何か描いてください。さあどうぞ!

……って言ってもまず何をしたらいいか分からないですよね。

順を追ってやっていきましょう。

最初はアタリってのをとります。顔とか関節がこの辺で~みたいなの。

なんか上手い人たちがキャラクターの顔に十字描いたりしてるの見たことないですか?アレです。

この作業に関しては『アタリ 絵』とかでggったらモリモリ出てきますのでそれを参考にしましょう。

ポーズは写真や好きなイラストを参考にしましょう。(但し他人の著作物を勝手にトレースした等の場合は私的利用の範囲に留めたほうがよさそう。せめて明記すべき)

アタリをとって肉付けしたのがこんな感じ↓

f:id:kackoh:20181209155746p:plain

アタリをとってちょっと肉付けした

ちなみに僕はクリスタでは鉛筆ツールの「濃い鉛筆」と消しゴムツールの「硬め」 だけでペン入れまでやります。設定だけメモっておくので参考までに。

・濃い鉛筆

  -ブラシサイズ 10.0 (長辺4000pxくらいの大きいキャンバスに描くので太め)

  -ブラシ濃度 90

  -手ブレ補正 35 他はデフォルト

・硬め(消しゴム)

  -全部デフォルトのまま、ブラシサイズだけ使いやすいようにその都度変える

 

 -------------------------

4.ラフ・下描き

 

資料を見ながらザクザクと描いていきます。ポイントは線の勢いを殺さないことです。チマチマ描いたり、そっと描くのは線が縒れてふにゃふにゃになりがちです。思い切りが大事。

模写をするときは元の絵をガン見しつつ描きます。それ以外の場合もポーズ集などの参考資料をいっぱい見ます。最初は(というかなんなら永遠に)何も見ないで描けるようになる必要はないです。

 

基本中の基本としてデジタルで描く際には『レイヤー』機能を使いましょう。これが使えるだけでめっちゃ捗ります。詳しくは公式からどうぞ↓

シリーズ「レイヤーの種類・操作」 by ClipStudioOfficial のTIPS一覧 - CLIP STUDIO TIPS

ある程度機能の揃ったペイントソフトには必ず入っている機能なので押さえておきましょう。その名の通りキャンバス上に層を作ることが出来る機能です。

 

ラフを描くときには、

→ラフ用の新規レイヤーを作成して、

→アタリを描いたレイヤーの上に重ね、

→アタリを描いたレイヤーの不透明度を10パーセントくらいにする

と描きやすいです。

 

ラフが描けたら下描きをして更に詳細を詰めます。別のレイヤーを作ってもいいですし、ラフ自体に加筆修正して整えていくのもオッケーかなあと思います。

 

ラフ・下描きはこんな感じ↓

f:id:kackoh:20181209163003p:plain

f:id:kackoh:20181209163008p:plain

ラフ・下描き

このくらい丁寧に描いておくと線画が楽です。時間がないときは下描きに色を付けていってもそれなりのものが出来るんですが今回は普通にペン入れしていきます。

 

  -------------------------

5.線画

 

ペン入れしていきますがここでもレイヤー分けをします。今度はアタリを描いたレイヤーを非表示にして、ラフのレイヤーの不透明度を10パーセント程度にし、線画用のレイヤーを作成してラフのレイヤーの上に重ねましょう。

線画は部位毎にレイヤーを分けるのもアリです。例えば顔のレイヤーと髪のレイヤーなどに分けると後から微調整がしやすくなります。

線は勢いを大切に、ラフをなぞるのではなく「描いた線がうまいこと下描きの線に乗った」という感覚です。なのでうまくいくまで線を引いてctrl+Z(取り消し)を繰り返します。

 

レイヤーにはラスターとベクターの二種類があるのですが、線画を描く際にはベクターレイヤーを使うと楽が出来ます。ベクターレイヤーの詳しい機能についてはここでは書きませんが、『消しゴム』ツールの『ベクター用』にある交点消しが重宝します。

 

で、ペン入れの際に線の強弱に気を付けろとかメリハリをしっかりとか言われがちなんですけどちょっと描いたくらいじゃ「そんなんわからん」ってなります。

そこで。

後から線のメリハリをつけ足します。

←before after→

f:id:kackoh:20181209201516p:plain
f:id:kackoh:20181209201520p:plain

 

別レイヤーを重ねて線の交点(隅っこ)に黒を塗ったり、線自体を二度書きすることでメリハリを出してます。僕にとって一発で勢いもメリハリもある線を描くのは非常に難しいのでこうやって誤魔化しているわけですね。

これだけでもかなり線画が引き締まるのでオススメです。

見えないところやとても暗いところ(顎下や後ろ髪の一部など)は思い切って黒で塗り潰します。見せなくていい部分の情報を削ることでメリハリを生みます。これでさらに線画がそれっぽく見えてきます。

 

実際の線画はこちら↓

 

f:id:kackoh:20181209203153p:plain

線画

 

 -------------------------

6.下塗り

 

色を塗るときにはバケツツールが便利です。解説は公式から↓

塗りつぶしツールを使いこなす①基本編「ツール設定・色塗り #4」 by ClipStudioOfficial - CLIP STUDIO TIPS

あとはペンツールからGペンやベタ塗りペンなどを活用して下色を塗っていきましょう。

 

下塗りのポイントは、

①必ず線画より下の層のレイヤーに塗ること

②はみ出しや塗り残しを潰すこと

③パーツごとに別レイヤーに塗る(推奨)、せめて色ごとには分けること

④最初に置く色はある程度適当でよし

といったところでしょうか。

①と②は当たり前なんですが、③をキチンとやっておくと後から楽です。というのも④と関係するのですが、クリスタには色調補正といってレイヤーごとに(厳密には選択範囲などでも可能)後から色を調整する機能が搭載されているからです。

 

下塗りをした後はこんな感じ↓

f:id:kackoh:20181209210556p:plain

下塗り

 

 -------------------------

7.彩色

 

ディテールを出していきます。

乗算レイヤーというのを使います。(この辺↓を参照)

tips.clip-studio.com

 

レイヤーの合成モードを変えることで様々な効果を得ることが出来ます。乗算レイヤーはそのレイヤーに乗っている色とそれより下のレイヤーの色を掛け合わせます。

色が濃くなると思ってくれればいいです。なので陰影をつける際などに役立ちます。

直下のレイヤーにのみ適用したい場合はクリッピングを使うといいです。はみ出さなくなります。これは乗算レイヤーで影を付ける際以外にも使えるので覚えておいて損はないです。

影をつけるときは描き足すよりも一気に塗って後から削る方がやりやすいと気がします。

 

個人的に気を付けているのは黒や灰色そのものを乗算しないこと(例外もあります)。彩度の存在する色を乗せたほうが見栄えが良くなります。金髪に灰色を乗算したときと明るい桃色*2を乗算したときの違いはこれ↓を見れば一目瞭然です。

f:id:kackoh:20181209214409p:plain
f:id:kackoh:20181209214411p:plain
右のほうが華やかだよね

一旦色を乗算で乗せてみてしっくりこなかったら、下塗りの時のように色調補正を乗算レイヤーに適用してみましょう。

 

もう一つ言うならば、幾分か主観の混じった意見ですがリッチな塗りを中途半端にやるとめちゃくちゃ恰好悪くなりがちです。

最初のうちはアニメ塗りに近い、パリっとした塗りの方が見栄えはよくなる気がします(もう一度言いますが主観が入ってます)。

 

細かい部分の塗りに関して言うことは特にありません。調べればいろんな方の書いた講座のページがあると思うので、自分なりの色塗りを見つけましょう。

 

あとは色トレスってやつですかね。線画の色をなじませるみたいな話です。

(一応僕のやり方は、線画のレイヤーを全部レイヤーフォルダに突っ込んでフォルダ単位で通常レイヤーをクリッピングして筆で塗って線色を変えてます。分からなかったら読み飛ばしていいです)

 

ハイライトやらを追加したり少し色を補正したりして完成です。

 

f:id:kackoh:20181209161515p:plain

完成

長文・駄文失礼いたしました。お読みいただきありがとうございます。

 

 -------------------------

おまけ

 

今回は使ってないんですが簡単に情報量を増すのに使えるズルを紹介します。

使うのは境界効果フチです(以下サイト参照)。

tips.clip-studio.com

 

これでズルをします。

色塗りが終わってからでも髪の毛のハネなどを加筆できるごり押しテクです。

 

まずは一番上にレイヤーを二枚追加します。

f:id:kackoh:20181209222622p:plain

①のレイヤーだけ下のように設定します。フチの太さは線画の太さと近づけましょう。若干細い方がいいかもしれません。フチの色は線画の色に合わせます。

f:id:kackoh:20181209222701p:plain

 

次にハネを加筆したい髪の毛部分の色をスポイトで取ります。(今回はハイライト部分なので白になりました)

①の方に太いペンで髪をつけ足します。すると境界効果で出来たフチが線画っぽくなります。

f:id:kackoh:20181209223541p:plain
f:id:kackoh:20181209223544p:plain
白いペンで描くだけで右みたいにつけ足せる

最後に同じ色のペンを使って②のレイヤーで①の要らない部分を削ります。

 

f:id:kackoh:20181209223920p:plain

加筆完了

境界効果は他にも悪用使用できて、

f:id:kackoh:20181209225104p:plain

生クリームだよ

 

あ、Masayoshi IimoriのHella Deepは最高なのでみんな聞きましょう。友達の曲が弐寺でプレーできるのめっちゃ不思議な気分になります。

ではでは。

 

(∠KOH)

*1:かわいい

*2:金髪には桃色系を乗せると映える

音ゲーって楽しいよね!

どうも∠KOH(KacKOH(かっこう))です。読みにくいHNでごめんね。

 

今回は僕の所属しているサークルB4UTでのちょっとした企画(以下参照)に参加しようと思い書きはじめました。

www51.atwiki.jp

 

 ** ** **

 

さてさて。

皆さん音ゲーって知ってますよね。音楽ゲーム。音感ゲームっても言うのかな。知らない人はggってみよう。

一般的によく知られているので言うと太鼓の達人とかああいうやつです。ちょっとお手軽なやつだとスクフェスとかデレステとかああいうのも含まれます。

 

で。

今回は「今からでも遅くない! 音ゲーを始めてみませんか?」ってお話です。

えっ、始めるのが怖い? バッカお前……俺がついてるだろ!

 

なんでこんな話をしようと思ったのかと言うと、自身もつい1年半前に音ゲーを始めた(比較的)歴の浅い音ゲーマーだからです。(2016年12月現在大学3年で音ゲー始めたのは大学2年の5月)

 

 ** ** **

 

まずは僕が音ゲーを始めたきっかけをつらつらと。

そもそも僕は某県の田舎出身なのでゲームセンターは身近なものではなく、「こわいところ」「ふりょうがいそう」「たばこくさい」「なんかやだ」といった印象しかありませんでした。

で、大学に入って東京に出て来てからもその価値観は拭えず、大学1年の頃はおうちにこもってパソコンを見ながらオタクスマイルをしていたわけです。今もやってるけど

 

そんな僕に転機が訪れます。

大学2年の4月、高校同期に連れられて秋葉原タイトーステーションにふらっと立ち入ったんですね。まあでもそれまでゲーセンなんて碌に行ったこともない訳ですから特にやりたいゲームがあるわけでもなく各フロアをウロウロしてたわけです。

するとこんなものがありまして↓

p.eagate.573.jp

QMAっていうクイズゲームですね。

高校時代にクイズ研究会なる組織に所属していた僕はこのゲームにちょっと惹かれました。友達も一緒だったので4人対戦をしてみることに。そして……

 

「このゲーム………………おもしろいやんけ!」

 

見事アーケードゲーム(以下ACゲームと略)に陥落します。当時は筐体に100円を入れるのさえ新鮮でした。おもしろいけどお金がどんどん無くなる不安を覚えたのも、もう懐かしい話です。

 

んでんで。

ACゲームのプレーデータを保存するには専用のカードが必要になるらしいことを知った僕はe-AMUSEMENT PASS――通称eパス』を購入します。実は交通系カードでも代用可能なんですが当時はそんなこと知りませんでしたので。

そんなこんなでACゲームデビューした僕ですがやっぱりゲームセンターはちょっとこわいところと言う気がしていて。

後日初めて独りでゲーセンを訪れた時はビビりまくりでした。本当はそんなにこわいところでもないのにね。小心者とでも笑ってください。

でも空きコマに大学を抜け出してゲーセン行ったりとかめっちゃ大学生っぽくて楽しかったです(今では日常となってしまったので息をするようにクズムーブメントしてますが)

 

ところで先ほど購入したeパス。なんとなく流れで買ったこいつが僕を音ゲーの道へと引きずり込みます。

それは学校帰りに友人と寄ったゲーセンにて。

 

友人A「最近これにハマってるんだよね」

友人B「最高におもしろい」

  僕「これは……」

p.eagate.573.jp

そう、jubeatです。4x4の光るパネルという独特なインタフェースを持つコイツに友人たちはハマっていたのです。

実は何を隠そう、eパスを使うとKONAMI音ゲーは初回無料(ゲーセンによるかも)でプレーできちゃうんですよね。すばらしい。

ということでちょっと恥ずかしがりつつも挑戦。そしてチュートリアルが始まりリズムに合わせてパネルをタッチするのですが……

 

>> B A D <<

……アレ?

 

全 く リ ズ ム に 合 っ て な い !

音ゲーでは普通、叩くごとに上手く叩けているかの判定が出るのですが、全く良い判定が出ません。僕に限らず、おそらく初めて触れる人だと本当に判定が合わなくて乾いた笑いが出ます。ともかくチュートリアルで良い判定が出ただけで僕より才能あります。

 

恐れ慄きつつも1曲目。jubeatはアニソンやJPOPも結構収録されてるのでそのラインナップにまず驚きます。マンウィズのGet Off of My Way入ってるとかマジで? って感じ。

まあでも1曲目は無難(?)にonly my railganを選曲。オタクだから仕方ないね。

知ってる曲に合わせて叩く。やっていることはそれだけなのですがこれがすごい楽しいんです。音楽聴きながらついリズムを取ったり口ずさんだりしちゃうことが結構あると思うんですけど、アレにゲーム性が加わる。そう考えるとなんか面白そうじゃないですか?

 

そして曲が終わりリザルト画面。ギリギリクリア! 嬉しい!

音ゲーは自分の腕前を点数化して示してくれます。これってテストとかスポーツとかに近いのかなあと思います。練習すれば上手くなって結果に反映される。記録を更新したら嬉しいに決まってますよね。

 

こんな感じで音ゲーデビューした僕はその魅力にどっぷりと浸かり、今ではjubeatだけでなく他の音ゲーもたくさんやってます。お金がたりないよぅ。

 

  ** ** **

 

そんな感じで音ゲーにハマった僕ですが、音ゲーが楽しいのは勿論のこととして他にも副次的な楽しみ(もしかしたらこっちの方が主かも)があるんです。

 

まず交友関係が確実に広がります音ゲーのお話が出来る人ってだけで音ゲーマーは喜びます。これは本当。

あ、音ゲーを始めたらtwitterとかも始めるといいです。嬉しかったリザルトを貼ったりする人が多いので、そういった人をフォローしていくと自然と仲良くなったりとかとか。

僕自身いつの間にか仙台やら神戸やらに知り合いが増えてびっくりしてます。これからもどうぞよろしく。

 

ふたつめは外出頻度が増えること。

これは引きこもりがちだった僕が毎日のようにゲームセンターへ行って帰りにラーメンを食べていることからも自明ですね。目的が出来れば外に出ます。

 

みっつめは新しい音楽と出会えること。

JPOPとかJROCKとか洋楽とかアニソンとかボカロとか、そういったジャンルとはまた違った音楽に出会えます。

僕も音ゲーを始める前までボーカルの無い曲の良さがあまり分からなかった(意外とこういう人多いのでは)のですが、今となってはガンガン鳴ってる曲のカッコ良さに痺れてます。少なくとも、音楽にもっと興味が湧いてくるのは事実です。

折角だし色んな音楽の良さを享受できたら最高じゃないですか(語彙力のNASA)。

 

あとはライバルと競い合うこと。

副次的と言うか、独りで黙々とやるより実力の近い人と競い合いながらやると楽しさが倍増しますよ、ってことです。なんだかんだ人間は勝った負けたが好きな動物なのでライバルと競い合う(公式のアプリでもそういった機能があります)のは本当に楽しい。

上達も早まりますしいいこと尽くしです。

 

などなど。

 

 ** ** **

 

はい。

ということで駄文でしたがこの辺りで〆たいなあと思います。

 

おそらくなんですが、音ゲー始めたての頃ってゲーセンに居る他の音ゲーマーが上手過ぎて萎縮しちゃうと思います。上手い人の後に並ぶとかやだなー、とか。僕はそうでした。

もしかしたら意地の悪いヤツが居るかもしれませんが、少なくとも僕は遭遇したことがありません。もっともそういう人たちはSNSで炎上してるようなことが多いので超少数でしょう。

普通の音ゲーマーは新規に始める人をそれなりに歓迎していると思います。twitterでアドバイスくれる人もいっぱいいますし。

 

あ、でもマナーは守りましょう。順番は守るとか台パンしないとか。そのくらいです。

 

では、最後にもう一度。

音ゲーは本当に楽しいです。押しつけがましくてもこの楽しさを知って欲しい。

 

今からでも遅くない! 音ゲーを始めてみませんか?

 

(∠KOH)

Rhythmboxで1曲ループを実現する −6−

今回Rhythmboxを手探ってみて感じたことは大きく2つです。

 

その1。

C言語gtk+がメインで読みやすい!

 

その2。

関数名や変数名がわかりやすい!

 

……とにかく読みやすいコードを書くのが大事なんだなあ、と漠然と思いました。

grepで引っ掛け易い関数名や変数名のおかげで後から弄るのが簡単でした。

しかしながら他者の書いたコードというのはその人の癖やこだわりみたいなものが入ると途端に読みづらくなりますね。(少し前に言及した文字列で状態管理してるヤツとか)

スタンダードな書き方というのを身につけたいものです。

 

以上、『大規模ソフトウェアを手探る』の考察でした。

Rhythmboxで1曲ループを実現する −5−

はい、ということで前回の続きです。

 

早速ですが、前回扱ったrb_shell_player_get_playback_state関数が使われているrb_shell_player_sync_control_state関数をいじります。

 

static void
rb_shell_player_sync_control_state (RBShellPlayer *player)
{
	gboolean shuffle, repeat;
	GAction *action;
	rb_debug ("syncing control state");

	if (!rb_shell_player_get_playback_state (player, &shuffle,
						 &repeat))
		return;


	action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
					     "play-shuffle");
	g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (shuffle));

	action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
					     "play-repeat");
	g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (repeat));
}

 ここはボタンの状態とそのフラグを同期させる処理を行う部分です。このままだとシャッフルか通常ループにした対応していないので、ここに1曲ループに関する記述を加えます。

 

また、シャッフルや通常ループがonの時、1曲ループはoffになり、1曲ループがonの時はその他はoffになっている方が自然です。

これも対応させましょう。

static void
rb_shell_player_sync_control_state (RBShellPlayer *player)
{
	gboolean shuffle, repeat, repeat1;
	GAction *action;
	rb_debug ("syncing control state");

	if (!rb_shell_player_get_playback_state_loop1 (player, &repeat1))
	{
		if (!rb_shell_player_get_playback_state (player, &shuffle,&repeat))
		{
			return;
		}
		action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
						     "play-shuffle");
		g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (shuffle));
	
		action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
						     "play-repeat");
		g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (repeat));

		repeat1 = 0;
		action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
					     "play-repeat1");
		g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (repeat1));

		return;
	}

	shuffle = 0;
	repeat = 0;
	loop1_flag = repeat1;

	action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
					     "play-shuffle");
	g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (shuffle));

	action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
					     "play-repeat");
	g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (repeat));

	action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
					     "play-repeat1");
	g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (repeat1));
}

 

こんな感じです。

 

何をしているかというと、

①現在の状態を調べる。それが"linear1"か"linear-loop1"のいずれかであればif文を素通り。別の状態であれば元々の処理(+1曲ループボタンをoffにする処理)を行って関数を抜ける。

②"linear1"か"linear-loop1"ならシャッフルボタンと通常ループボタンをoffにし、かつ、1曲ループボタンをフラグに合わせて関数を抜ける。

と言った具合です。

 

ふぅ。

 

これで大きな部分は終わりです。細かいところを直していきます。

まずは念の為loop1_flagを初期化。

前回state_to_play_order_loop1を書いた次の行あたりに、

gboolean loop1_flag = FALSE;

と一行書きましょう。

 

次。

play_repeat_action_cbとplay_shuffle_action_cb内でloop1_flagをFALSEにします。

static void
play_repeat_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
	RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
	const char *neworder;
	gboolean shuffle = FALSE;
	gboolean repeat = FALSE;
	rb_debug ("repeat changed");

	if (player->priv->syncing_state)
		return;

	rb_shell_player_get_playback_state (player, &shuffle, &repeat);

	repeat = !repeat;
	loop1_flag = FALSE;
	
	neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
	g_settings_set_string (player->priv->settings, "play-order", neworder);
}

 

static void
play_shuffle_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
	RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
	const char *neworder;
	gboolean shuffle = FALSE;
	gboolean repeat = FALSE;

	if (player->priv->syncing_state)
		return;

	rb_debug ("shuffle changed");

	rb_shell_player_get_playback_state (player, &shuffle, &repeat);

	shuffle = !shuffle;
	loop1_flag = FALSE;
	
	neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
	g_settings_set_string (player->priv->settings, "play-order", neworder);
}

 

更にもうひとつ。

rb_shell_player_init関数内で、

(前略)

    rb_shell_player_add_play_order (player, "linear", N_("Linear"),
					RB_TYPE_LINEAR_PLAY_ORDER, FALSE);
    rb_shell_player_add_play_order (player, "linear-loop", N_("Linear looping"),
					RB_TYPE_LINEAR_PLAY_ORDER_LOOP, FALSE);
    rb_shell_player_add_play_order (player, "linear1", N_("Linear1"),
					RB_TYPE_LINEAR_PLAY_ORDER, FALSE);
    rb_shell_player_add_play_order (player, "linear-loop1", N_("Linear looping one"),
					RB_TYPE_LINEAR_PLAY_ORDER, FALSE);
    rb_shell_player_add_play_order (player, "shuffle", N_("Shuffle"),
					RB_TYPE_SHUFFLE_PLAY_ORDER, FALSE);
(後略)

 

他と対応するように青字を追加します。

これでrb-shell-player.cに対する変更は終わりです。

 

最後の最後。

rb-shell-player.hで関数を宣言するのとrb-shell.cにボタンの構築に関する記述をして実装完了です。長かった……。

 

ではrb-shell-player.h内で

(前略)
gboolean                rb_shell_player_get_playback_state(RBShellPlayer *player,
							   gboolean *shuffle,
							   gboolean *repeat);

gboolean                rb_shell_player_get_playback_state_loop1(RBShellPlayer *player,
							   	 gboolean *repeat1);
(後略)

 青字部分を宣言します。

 

そしてrb-shell.c内のconstruct_load_ui関数にて、

static void
construct_load_ui (RBShell *shell)
{

(中略)

	/* this seems a bit unnecessary */
	gtk_actionable_set_action_target_value (GTK_ACTIONABLE (gtk_builder_get_object (builder, "shuffle-button")),
						g_variant_new_boolean (TRUE));
	gtk_actionable_set_action_target_value (GTK_ACTIONABLE (gtk_builder_get_object (builder, "repeat-button")),
						g_variant_new_boolean (TRUE));
	gtk_actionable_set_action_target_value (GTK_ACTIONABLE (gtk_builder_get_object (builder, "repeat1-button")),
						g_variant_new_boolean (TRUE));
(後略)

 

……はい。

これで完成です!!

やったあ!!

 

次回はまとめとちょっとしたおまけです。

Rhythmboxで1曲ループを実現する −4−

今回はフラグとボタンを同期させます。

これが終われば実装は終了です。

ちょっと長いので記事を2回に分けて書こうと思います。

 

見ていくのはrhythmbox-3.0.2/shell/rb-shell-player.h、rhythmbox-3.0.2/shell/rb-shell-player.c、rhythmbox-3.0.2/shell/rb-shell.cの3つです。

 

とりあえずrb-shell-player.cを見ていきましょう。

 

まず気になるのがrb_shell_player_constructed関数です。名前からしてプレイヤーを構成するための重要な関数であることがわかります。

static void
rb_shell_player_constructed (GObject *object)
{
	RBApplication *app;
	RBShellPlayer *player;
	GAction *action;

	GActionEntry actions[] = {
		{ "play", play_action_cb },
		{ "play-previous", play_previous_action_cb },
		{ "play-next", play_next_action_cb },
		{ "play-repeat", play_repeat_action_cb, "b", "false" },
		{ "play-shuffle", play_shuffle_action_cb, "b", "false" },
		{ "volume-up", play_volume_up_action_cb },
		{ "volume-down", play_volume_down_action_cb }
	};

	RB_CHAIN_GOBJECT_METHOD (rb_shell_player_parent_class, constructed, object);

	player = RB_SHELL_PLAYER (object);

	app = RB_APPLICATION (g_application_get_default ());
	g_action_map_add_action_entries (G_ACTION_MAP (app),
					 actions,
					 G_N_ELEMENTS (actions),
					 player);

	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>p", "app.play", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Left", "app.play-previous", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Right", "app.play-next", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Up", "app.volume-up", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Down", "app.volume-down", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>r", "app.play-repeat", g_variant_new_boolean (TRUE));
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>u", "app.play-shuffle", g_variant_new_boolean (TRUE));

	(後略)

 

ここでは以前書き換えたuiファイルにおけるボタンのaction_nameプロパティに言及しているようです。そこでapp.play-repeat1に対する記述を書き加えます。

static void
rb_shell_player_constructed (GObject *object)
{
	RBApplication *app;
	RBShellPlayer *player;
	GAction *action;

	GActionEntry actions[] = {
		{ "play", play_action_cb },
		{ "play-previous", play_previous_action_cb },
		{ "play-next", play_next_action_cb },
		{ "play-repeat", play_repeat_action_cb, "b", "false" },
		{ "play-repeat1", play_repeat1_action_cb, "b", "false" },
		{ "play-shuffle", play_shuffle_action_cb, "b", "false" },
		{ "volume-up", play_volume_up_action_cb },
		{ "volume-down", play_volume_down_action_cb }
	};

	RB_CHAIN_GOBJECT_METHOD (rb_shell_player_parent_class, constructed, object);

	player = RB_SHELL_PLAYER (object);

	app = RB_APPLICATION (g_application_get_default ());
	g_action_map_add_action_entries (G_ACTION_MAP (app),
					 actions,
					 G_N_ELEMENTS (actions),
					 player);

	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>p", "app.play", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Left", "app.play-previous", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Right", "app.play-next", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Up", "app.volume-up", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Down", "app.volume-down", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>r", "app.play-repeat", g_variant_new_boolean (TRUE));
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>1", "app.play-repeat1", g_variant_new_boolean (TRUE));
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>u", "app.play-shuffle", g_variant_new_boolean (TRUE));

	(後略)

 

これで1曲ループボタンが押された時にplay_repeat1_action_cbという関数を呼び出すことが出来るようになりました。(ついでにCtrl+1をショートカットに割り当てています)

しかしplay_repeat1_action_cb関数はまだ作成していないので書き加えていきます。

一から書くのは骨が折れるのでplay_repeat_action_cb関数を雛形にしちゃいましょう。

static void
play_repeat_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
	RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
	const char *neworder;
	gboolean shuffle = FALSE;
	gboolean repeat = FALSE;
	rb_debug ("repeat changed");

	if (player->priv->syncing_state)
		return;

	rb_shell_player_get_playback_state (player, &shuffle, &repeat);

	repeat = !repeat;
	neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
	g_settings_set_string (player->priv->settings, "play-order", neworder);
}

 

見た限りではボタンの状態を取得してそれを反転させる操作をしています。ここで少々問題になるのが太字で示した部分です。

これらの定義を見てみます。

 

[rb_shell_player_get_playback_state]

gboolean
rb_shell_player_get_playback_state (RBShellPlayer *player,
				    gboolean *shuffle,
				    gboolean *repeat)
{
	int i, j;
	char *play_order;

	play_order = g_settings_get_string (player->priv->settings, "play-order");
	for (i = 0; i < G_N_ELEMENTS(state_to_play_order); i++)
		for (j = 0; j < G_N_ELEMENTS(state_to_play_order[0]); j++)
			if (!strcmp (play_order, state_to_play_order[i][j]))
				goto found;

	g_free (play_order);
	return FALSE;

found:
	if (shuffle != NULL) {
		*shuffle = i > 0;
	}
	if (repeat != NULL) {
		*repeat = j > 0;
	}
	g_free (play_order);
	return TRUE;
}

 

[state_to_play_order]

static const char* const state_to_play_order[2][2] =
	{{"linear",	"linear-loop"},
	 {"shuffle",	"random-by-age-and-rating"}};

 

rb_shell_player_get_playback_state関数はシャッフルと通常ループの状態をフラグとして得ます。返り値は現在の状態がstate_to_play_orderに登録されているものであればTRUE、そうでなければFALSEです。

state_to_play_orderは見ての通りフラグと文字列とを対応付ける配列です。(なぜわざわざ文字列を使うのかは疑問ですが……)

 

さて、この関数と配列はもちろん1曲ループには対応していませんので同じようなものを1曲ループ用に書き加えねばなりません。大変。

 

とはいえ雛形として元の関数を利用すれば意外に楽ちんです。まずはそれぞれをコピーしてその直後にペーストしましょう。

コピペした方を改変していきます。

 

[rb_play_repeat1_action_cb]

static void
play_repeat1_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
	RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
	const char *neworder;
	gboolean repeat1 = FALSE;
	rb_debug ("repeat1 changed");

	if (player->priv->syncing_state)
		return;

	rb_shell_player_get_playback_state_loop1 (player, &repeat1);

	repeat1 = !repeat1;
	loop1_flag = repeat1;
	neworder = state_to_play_order_loop1[repeat1 ? 1 : 0];
	g_settings_set_string (player->priv->settings, "play-order", neworder);
}

 

[rb_shell_player_get_playback_state_loop1]

gboolean
rb_shell_player_get_playback_state_loop1 (RBShellPlayer *player,
				    gboolean *repeat1)
{
	int i;
	char *play_order;

	play_order = g_settings_get_string (player->priv->settings, "play-order");
	for (i = 0; i < 2; i++)
		if (!strcmp (play_order, state_to_play_order_loop1[i]))
			goto found;

	g_free (play_order);
	return FALSE;

found:
	if (repeat1 != NULL) {
		*repeat1 = i > 0;
	}
	g_free (play_order);
	return TRUE;
}

 

[state_to_play_order_loop1]

static const char* const state_to_play_order_loop1[2] =
	{"linear1",	"linear-loop1"};

 

青字の部分が変更点です。"linear1"というのは"linear"と同じ動作をするのですが念の為分けて置きました。蛇足かも?

 

ここまできたらもう一息です。

続きは次回。

Rhythmboxで1曲ループを実現する −3−

今回は1曲ループのための関数を実装します。

 

簡単に説明すると、

リニア再生(順番通りに再生するヤツ)の関数内で次の曲/前の曲へ飛ぶとき、代わりに今再生している曲へ飛ばしちゃえばよくない?

ってことです。

 

編集するファイルはrhythmbox-3.0.2/shell/rb-play-order.hとrhythmbox-3.0.2/shell/rb-play-order-linear.cの2つです。

 

まずはrb-play-order.hで1曲ループを切り替えるためのフラグを宣言します。

ここは簡単で、

RBShellPlayer *      rb_play_order_get_player        (RBPlayOrder *porder);
RBSource *           rb_play_order_get_source        (RBPlayOrder *porder);
RhythmDB *           rb_play_order_get_db            (RBPlayOrder *porder);
RhythmDBQueryModel * rb_play_order_get_query_model   (RBPlayOrder *porder);
gboolean             rb_play_order_model_not_empty   (RBPlayOrder *porder);

gboolean             rb_play_order_player_is_playing (RBPlayOrder *porder);

gboolean loop1_flag;

 

関数宣言をしている箇所の後に青字部分を追加するだけです。

書き加えたら保存して閉じてOKです。

 

続いてrb-play-order-linear.c。

開いたらまずrb_linear_play_order_get_next関数とrb_linear_play_order_get_previous関数を探しましょう。この2つの関数は名前の通りそれぞれ次曲/前曲を取得する関数です。

static RhythmDBEntry *
rb_linear_play_order_get_next (RBPlayOrder *porder)
{
	RhythmDBQueryModel *model;
	RhythmDBEntry *entry;

	g_return_val_if_fail (porder != NULL, NULL);
	g_return_val_if_fail (RB_IS_LINEAR_PLAY_ORDER (porder), NULL);

	model = rb_play_order_get_query_model (porder);
	if (model == NULL)
		return NULL;

        entry = rb_play_order_get_playing_entry (porder);
	if (entry != NULL) {
		RhythmDBEntry *next;
		next = rhythmdb_query_model_get_next_from_entry (model, entry);
		rhythmdb_entry_unref (entry);
		return next;
	} else {
		GtkTreeIter iter;
		if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
			return NULL;
		return rhythmdb_query_model_iter_to_entry (model, &iter);
	}
}

 

太字部分が実際に次曲の情報を取得している部分です。entryが現在の曲情報、nextが次曲の情報であるので1曲ループにするにはずばり、

 

next = entry;

 

とすれば良いですね!(すごく……雑です……)

これを先ほど宣言したフラグと合わせて書き換えると、

static RhythmDBEntry *
rb_linear_play_order_get_next (RBPlayOrder *porder)
{
	RhythmDBQueryModel *model;
	RhythmDBEntry *entry;

	g_return_val_if_fail (porder != NULL, NULL);
	g_return_val_if_fail (RB_IS_LINEAR_PLAY_ORDER (porder), NULL);

	model = rb_play_order_get_query_model (porder);
	if (model == NULL)
		return NULL;

        entry = rb_play_order_get_playing_entry (porder);
	if (entry != NULL) {
		RhythmDBEntry *next;
		if (loop1_flag == TRUE)
		{
			next = entry;
		}
		else
		{
			next = rhythmdb_query_model_get_next_from_entry (model, entry);
			rhythmdb_entry_unref (entry);
		}
		return next;
	} else {
		GtkTreeIter iter;
		if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
			return NULL;
		return rhythmdb_query_model_iter_to_entry (model, &iter);
	}
}

 

フラグがTRUEなら1曲ループにする、という実装です。(Cではbooleanが扱えませんがgtk+ではgboolean型が定義されておりbooleanを扱うことが可能です)ということで同じような変更をrb_linear_play_order_get_previousにも行います。

static RhythmDBEntry *
rb_linear_play_order_get_previous (RBPlayOrder *porder)
{
	RhythmDBQueryModel *model;
	RhythmDBEntry *entry, *prev;

	g_return_val_if_fail (porder != NULL, NULL);
	g_return_val_if_fail (RB_IS_LINEAR_PLAY_ORDER (porder), NULL);

	model = rb_play_order_get_query_model (porder);
	if (model == NULL)
		return NULL;

        entry = rb_play_order_get_playing_entry (porder);
	if (entry == NULL)
		return NULL;

	if (loop1_flag == TRUE)
	{
		prev = entry;
	}
	else
	{
		prev = rhythmdb_query_model_get_previous_from_entry (model, entry);
		rhythmdb_entry_unref (entry);
	}

	return prev;
}

 

これで1曲ループ関数の実装は終了です。(まだフラグの値を操作していないのでこのままでは動かないことに注意!)

 \

次回はボタンとフラグを同期する操作へ入っていきます。

Rhythmboxで1曲ループを実現する −2−

今度こそ中身を見ていきます。

 

それでは早速Rhythmboxのソースをダウンロードして解凍しましょう。

今回扱うRhythmboxのバージョンは3.0.2です。しかし後述する改変を行えば3.4.1でも問題なく動作しますのでご安心を。

 

解凍するとrhythmbox-3.0.2というディレクトリが出来ると思います。

まず手始めに1曲ループ用のボタンを配置します。

 バックアップを取っておくと◎

 

rhythmbox-3.0.2/data/ui/main-toolbar.uiテキストエディタで開きます。

ここで記述されているのは、

f:id:kackoh:20161020122010p:plain

ここらへんのUIです(適当)

 

実際に中身を見るとまずこんな記述があると思います。

<object class="GtkImage" id="image1">
  <property name="visible">True</property>
  <property name="pixel_size">24</property>
  <property name="icon_name">media-playlist-shuffle-symbolic</property>
</object>

 

ここではgtk+を用いてアイコン画像を作っています。注目して頂きたいのは4行目のmedia-playlist-shuffle-symbolicという部分です。

当初、なんらかの画像ファイルを指定したりしてアイコンにしているのかな? と思ったのですが、どうやらgtk+に組み込まれたアイコンデータがあるようです。

末尾が-symbolicとなっているものは基本的にこういったアイコンデータを指しているみたいですね。なるほどなるほど。

 

また、1行目ではGtkImageクラスのオブジェクトにIDとしてimage1を振っています。続けて見ていくと同じようなものがimage1〜image5まで存在していることが確認できると思うので、ここにimage6として1曲ループボタン(のアイコン)を定義します。

(前略)
<object class="GtkImage" id="image5">
  <property name="visible">True</property>
  <property name="can_focus">False</property>
  <property name="xpad">8</property>
  <property name="ypad">9</property>
  <property name="pixel_size">24</property>
  <property name="icon_name">media-skip-backward-symbolic</property>
</object>
<object class="GtkImage" id="image6">
  <property name="visible">True</property>
  <property name="can_focus">False</property>
  <property name="pixel_size">24</property>
  <property name="icon_name">media-playlist-repeat-song-symbolic</property>
</object>
(後略)

 

青文字の部分を追加すればOKです。media-playlist-repeat-song-symbolicなんていうアイコンが用意されているのに1曲ループを実装しないRhythmboxさんェ……

 

次はボタンを配置します。main-toolbar.uiの以下の部分↓

<child>
  <object class="GtkToggleButton" id="repeat-button">
    <property name="visible">True</property>
    <property name="can_focus">True</property>
    <property name="receives_default">True</property>
    <property name="action_name">app.play-repeat</property>
    <property name="image">image2</property>
    <style>
      <class name="raised" />
    </style>
  </object>
  <packing>
    <property name="expand">False</property>
    <property name="fill">True</property>
    <property name="position">0</property>
  </packing>
</child>
<child>
  <object class="GtkToggleButton" id="shuffle-button">
    <property name="visible">True</property>
    <property name="can_focus">True</property>
    <property name="receives_default">True</property>
    <property name="action_name">app.play-shuffle</property>
    <property name="image">image1</property>
    <style>
      <class name="raised" />
    </style>
  </object>
  <packing>
    <property name="expand">False</property>
    <property name="fill">True</property>
    <property name="position">1</property>
  </packing>
</child>

 

 これを次のように変えます。

<child>
  <object class="GtkToggleButton" id="repeat1-button">
    <property name="visible">True</property>
    <property name="can_focus">True</property>
    <property name="receives_default">True</property>
    <property name="action_name">app.play-repeat1</property>
    <property name="image">image6</property>
    <style>
      <class name="raised" />
    </style>
  </object>
  <packing>
    <property name="expand">False</property>
    <property name="fill">True</property>
    <property name="position">0</property>
  </packing>
</child>
<child>
  <object class="GtkToggleButton" id="repeat-button">
    <property name="visible">True</property>
    <property name="can_focus">True</property>
    <property name="receives_default">True</property>
    <property name="action_name">app.play-repeat</property>
    <property name="image">image2</property>
    <style>
      <class name="raised" />
    </style>
  </object>
  <packing>
    <property name="expand">False</property>
    <property name="fill">True</property>
    <property name="position">1</property>
  </packing>
</child>
<child>
  <object class="GtkToggleButton" id="shuffle-button">
    <property name="visible">True</property>
    <property name="can_focus">True</property>
    <property name="receives_default">True</property>
    <property name="action_name">app.play-shuffle</property>
    <property name="image">image1</property>
    <style>
      <class name="raised" />
    </style>
  </object>
  <packing>
    <property name="expand">False</property>
    <property name="fill">True</property>
    <property name="position">2</property>
  </packing>
</child>

 

これで1曲ループ用のボタンが配置できます。positionは0を左端としてどの順番で並べるかを決めるオプションです。今回は[1曲ループ][ループ][シャッフル]の順で並べています。

 

これでmain-toolbar.uiに対する変更は完了です。

次回は1曲ループ動作を実現します。