[xtone] iPhoneアプリを公開しました

Standard

7月から兼業フリーランスという働き方をしています。これについてはいろいろ思うところがあるので別にエントリーを書こうと思います。
@k13style こと三登さんとxtoneというiPhoneアプリを作ったので今日はその宣伝をします。
 
 

次世代音楽サジェストアプリ xtone

http://x-tone.org/
読み方は「クロストーン」です。「エックストーン」でも「ペケトン」でもありません。

iPhoneに入っているアルバムを2つミックスして、あなたにピッタリな音楽をおすすめします。
操作はアルバムをフリックするだけ。
xtoneがあなたの好きな音楽を分析して、新たな曲を見つけてくれます。

自分の持っているアルバムを2枚選ぶと、おすすめのアルバムを3枚教えてくれます。iTunes Storeにそのアルバムがあればアプリ内で試聴が可能です。みんなのミックス履歴も見れるので、そこから自分の知らない音楽に出会うこともできます。あと、スタッフ(三登とボク)が今聴いている曲もムダに教えてくれますw
 
三登さんがコンセプトとUIデザイン + フロントエンドの開発、ボクがバックエンド + フロントエンドの開発を担当しました。UIに関しては、2人でカッコいいと思った動きをふんだんに取り込んでいます。フロントは9割ぐらいHTML5とCSS3で書けました。仕事後にボクらのカフェ・Miyamaに通いつめて、だいたい1ヶ月ぐらいで1.0.0をリリースしたので、敷居下がったよなってのをすごく感じました。2年前に仕事でがっつりiOSアプリ作ったときはすごく辛かったのに。
CocoaPodscocoa CONTROLSの存在は偉大です。
 
 

おすすめのアルゴリズム

次世代音楽サジェストアプリと謳ってるのですが、おすすめを選ぶときに何を基準に選んでるかっていう話です。

xtoneのおすすめアルゴリズムは音楽のジャンルをベースにしたものではありません。
「音楽を勧める」ことに向き合い「似ている音楽とは何か」を考えました。
xtoneがピックアップするのは音楽ではなく、「ユーザー」です。
この独自アルゴリズムにより、ユーザーが求めている思いがけない音楽を発見できるようにつくりました。

ちょっとカッコよく書いてるんですが、実はすごくシンプルで、「あなたが選んだアルバムをすごい聴いてる人が聴いてる他のアルバムって、あなたも好きかもしれないよね」っていう考えがベースです。いろんな人のアルバムの情報が集まれば集まるほど、面白いおすすめが提供される可能性も上がってくっていう仕組みが作れたので、しばらくはアルバムデータベースの成長を見守りながらアルゴリズムを調整していこうと思います。
 
 

名刺代わりのアプリ

受託メインで仕事をしていると、業界が違う人から「どんな仕事してるの?」って聞かれたときに地味に困ったりします。今までもいろんなWEBサービスとかアプリを作り捨てしてきたんですが、今回は長く成長させていけそうなプロダクトができたので、「これ作ってます」って言ってこうと思います。上でも書きましたが、フリーランスとして仕事も受けていくことにしたので、名刺代わりにステッカーでも配ったらいいかなと。。w
 
 

まだまだやることたくさん

メモリを大量に消費してしまったり、おすすめのされるアルバムが同じものばっかりだったり、改良すべき点が多いです。。当面の目標は、アルバム情報10万枚と、アプリレビューで★5つを獲得することです。(ちなみに今は★1つの酷評が1つだけ)
たまーにアプリを立ち上げて、ボロクソに酷評したフィードバックとか送ってもらえるとうれしいです。
 
 

P.S.

アプリの審査通ったタイミングで三登さんとボクで同時にFacebookで告知したんですけど、コミュ力格差社会っていうのを垣間見た気がしたね。ボクの友達のほとんどは食べ物にしか反応してくれないw

Android マーケットにあげてみた4

Standard

https://market.android.com/details?id=com.bugcloud.android.vg
 
How to use
 
First of all, set your glitch settings following [menu] -> [setting].
 
setting1

Setting

 
Unless you checked “LAUGHING GLITCH”, you can see normal glitch in camera view.
Setting values of “MIN” and “MAX” will be used in glitching.(When the value of “MIN” is greater than “MAX”, you will see no effects.) The selector of “GLITCH LEVEL” can change level of glitch. You can choose it.
 
camera view

Camera View

 
 
camera view 2

Camera View2

 
 
If you checked “LAUGHING GLITCH”, you can see glitching with face detection.
 
camera view 3

Camera View3

 
Strictly speaking, this effect is not glitch but shuffle. I want to change this until the next version up.
 
That’s all.
This application is so heavy… I should solve this task at next version up.

Facebookタイムラインのカバー画像をCanvasで作る

Standard

.cover-gen

class Global
  constructor: () ->
    @canvas
    @context
  init: () ->
    @canvas = document.getElementById 'myCanvas'
    @context = @canvas.getContext '2d'

class Drawer
  constructor: () ->
    @bgImage = "img/def.png"
    @bgShadowBlur = 50
    @circleRadius = 20
    @mainColor = "#000000"
    @anotherColor = "#000000"
    @main = 1
    @sub = 2

  drawCircles: () ->
    x = @circleRadius * 1.8
    y = @circleRadius * 2
    cnt = 0
    for r in [0..5]
      for c in [0..12]
        if cnt % 9 is 0
          switch @sub
            when 1 then this.drawCircle(x, y, @anotherColor)
            when 2 then this.drawSpade(x, y-@circleRadius, @circleRadius*2, @circleRadius*2, @anotherColor)
            when 3 then this.drawDiamond(x, y-@circleRadius, @circleRadius*2, @circleRadius*2, @anotherColor)
            when 4 then this.drawHeart(x, y-@circleRadius, @circleRadius*2, @circleRadius*2, @anotherColor)
            when 5 then this.drawClub(x, y-@circleRadius, @circleRadius*2, @circleRadius*2, @anotherColor)
            else this.drawCircle(x, y, @anotherColor)

        else
          switch @main
            when 1 then this.drawCircle(x, y, @mainColor)
            when 2 then this.drawSpade(x, y-@circleRadius, @circleRadius*2, @circleRadius*2, @mainColor)
            when 3 then this.drawDiamond(x, y-@circleRadius, @circleRadius*2, @circleRadius*2, @mainColor)
            when 4 then this.drawHeart(x, y-@circleRadius, @circleRadius*2, @circleRadius*2, @mainColor)
            when 5 then this.drawClub(x, y-@circleRadius, @circleRadius*2, @circleRadius*2, @mainColor)
            else this.drawCircle(x, y, @mainColor)

        x += @circleRadius*3.8
        cnt += 1
      x = if r%2 is 0 then -1*@circleRadius*2 + @circleRadius*1.8 else @circleRadius*1.8
      y += @circleRadius*4

  drawCircle: (x, y, color) ->
    me = this
    ctx = _g.context
    canvas = _g.canvas
    ctx.save()
    ctx.fillStyle = color
    ctx.shadowColor = "rgba(0,0,0,1)"
    ctx.shadowBlur = getBlurValue(me.circleRadius)
    ctx.shadowOffsetX = 0
    ctx.shadowOffsetY = 0
    ctx.beginPath()
    ctx.arc(x, y, me.circleRadius, 0 , 2*Math.PI, false)
    ctx.fill()
    ctx.restore()

  drawDiamond: (x, y, width, height, color) ->
    me = this
    ctx = _g.context
    canvas = _g.canvas
    ctx.save()
    ctx.beginPath()
    ctx.moveTo(x, y)
    ctx.lineTo(x - width/2, y + height/2)
    ctx.lineTo(x, y + height)
    ctx.lineTo(x + width/2, y + height/2)
    ctx.closePath()
    ctx.fillStyle = color
    ctx.shadowColor = "rgba(0,0,0,1)"
    ctx.shadowBlur = getBlurValue(me.circleRadius)
    ctx.shadowOffsetX = 0
    ctx.shadowOffsetY = 0
    ctx.fill()
    ctx.restore()

  drawSpade: (x, y, width, height, color) ->
    me = this
    ctx = _g.context
    canvas = _g.canvas
    ctx.save()
    bottomWidth = width * 0.7
    topHeight = height * 0.7
    bottomHeight = height * 0.3
    ctx.fillStyle = color
    ctx.shadowColor = "rgba(0,0,0,1)"
    ctx.shadowBlur = getBlurValue(me.circleRadius)
    ctx.shadowOffsetX = 0
    ctx.shadowOffsetY = 0

    ctx.beginPath()
    ctx.moveTo(x,y)

    ctx.bezierCurveTo(x, y + topHeight/2, x - width/2, y + topHeight/2, x - width/2, y + topHeight)
    ctx.bezierCurveTo(x - width/2, y + topHeight * 1.3, x, y + topHeight*1.3, x, y + topHeight)
    ctx.bezierCurveTo(x, y + topHeight*1.3, x + width/2, y + topHeight*1.3, x + width/2, y + topHeight)
    ctx.bezierCurveTo(x + width/2, y + topHeight/2, x, y + topHeight/2, x, y)
    ctx.closePath()
    ctx.fill()

    ctx.beginPath()
    ctx.moveTo(x, y + topHeight)
    ctx.quadraticCurveTo(x, y + topHeight + bottomHeight, x - bottomWidth/2, y + topHeight + bottomHeight)
    ctx.lineTo(x + bottomWidth/2, y + topHeight + bottomHeight)
    ctx.quadraticCurveTo(x, y + topHeight + bottomHeight, x, y + topHeight)
    ctx.closePath()
    ctx.fill()
    ctx.restore()

  drawClub: (x, y, width, height, color) ->
    me = this
    ctx = _g.context
    canvas = _g.canvas
    ctx.save()
    circleRadius = width * 0.3
    bottomWidth = width * 0.5
    bottomHeight = height * 0.35
    ctx.shadowColor = "rgba(0,0,0,1)"
    ctx.shadowBlur = getBlurValue(me.circleRadius)
    ctx.shadowOffsetX = 0
    ctx.shadowOffsetY = 0
    ctx.fillStyle = color

    ctx.beginPath()
    ctx.arc(x, y + circleRadius + (height * 0.05), circleRadius, 0, Math.PI * 2, false)
    ctx.fill()

    ctx.beginPath()
    ctx.arc(x + circleRadius, y + (height * 0.6), circleRadius, 0, Math.PI * 2, false)
    ctx.fill()

    ctx.beginPath()
    ctx.arc(x - circleRadius, y + (height * 0.6), circleRadius, 0, Math.PI * 2, false)
    ctx.fill()

    ctx.beginPath()
    ctx.arc(x, y + (height * 0.5), circleRadius/2, 0, Math.PI * 2, false)
    ctx.fill()

    ctx.moveTo(x, y + (height * 0.6))
    ctx.quadraticCurveTo(x, y + height, x - bottomWidth/2, y + height)
    ctx.lineTo(x + bottomWidth/2, y + height)
    ctx.quadraticCurveTo(x, y + height, x, y + (height * 0.6))
    ctx.closePath()
    ctx.fill()

    ctx.restore()

  drawHeart: (x, y, width, height, color) ->
    me = this
    ctx = _g.context
    canvas = _g.canvas
    ctx.save()
    ctx.shadowColor = "rgba(0,0,0,1)"
    ctx.shadowBlur = getBlurValue(me.circleRadius)
    ctx.shadowOffsetX = 0
    ctx.shadowOffsetY = 0
    ctx.beginPath()
    topCurveHeight = height * 0.3
    ctx.moveTo(x, y + topCurveHeight)
    ctx.bezierCurveTo(x, y, x - width/2, y , x - width/2, y + topCurveHeight)
    ctx.bezierCurveTo(x - width/2, y + (height + topCurveHeight)/2 , x, y + (height + topCurveHeight)/2, x, y + height)
    ctx.bezierCurveTo(x, y + (height + topCurveHeight)/2, x + width/2, y + (height + topCurveHeight)/2, x + width/2, y + topCurveHeight)
    ctx.bezierCurveTo(x + width/2, y, x, y , x, y + topCurveHeight)
    ctx.closePath()
    ctx.fillStyle = color
    ctx.fill()
    ctx.restore()

  redraw: () ->
    _g.context.clearRect(0, 0, _g.canvas.width, _g.canvas.height)
    this.draw()

  draw: () ->
    me = this
    ctx = _g.context
    canvas = _g.canvas
    img = new Image()
    img.onload = () ->
      ctx.save()
      pattern = _g.context.createPattern(img, "repeat")
      ctx.fillStyle = pattern
      ctx.beginPath()
      ctx.rect(0, 0, canvas.width, canvas.height)
      ctx.fill()
      ctx.shadowColor = "rgba(0,0,0,1)"
      ctx.shadowBlur = getBlurValue(me.bgShadowBlur)
      ctx.shadowOffsetX = 0
      ctx.shadowOffsetY = 0
      #inset shadow on top
      ctx.translate(0, -1 * canvas.height)
      ctx.beginPath()
      ctx.rect(0, 0, canvas.width, canvas.height)
      ctx.fill()
      #inset shadow on bottom
      ctx.translate(0, 2*canvas.height)
      ctx.beginPath()
      ctx.rect(0, 0, canvas.width, canvas.height)
      ctx.fill()
      #inset shadow on left
      ctx.translate(-1 * canvas.width, -1 * canvas.height)
      ctx.beginPath()
      ctx.rect(0, 0, canvas.width, canvas.height)
      ctx.fill()
      #inset shadow on right
      ctx.translate(2 * canvas.width, 0)
      ctx.beginPath()
      ctx.rect(0, 0, canvas.width, canvas.height)
      ctx.fill()
      ctx.restore()

      me.drawCircles()

    img.src = @bgImage

_g = new Global()
_drawer = new Drawer()

$ ->
  _g.init()
  _drawer.draw()

  $("#colorSelector").ColorPicker {
    color: '#000000',
    onShow: (colpkr) ->
      $(colpkr).fadeIn(500)
      return false
    onHide: (colpkr) ->
      $(colpkr).fadeOut(500)
      _drawer.redraw()
      return false
    onChange: (hsb, hex, rgb) ->
      $("#colorSelector div").css('background-color', "##{hex}")
      _drawer.mainColor = "##{hex}"
  }

  $("#colorSelector2").ColorPicker {
    color: '#000000',
    onShow: (colpkr) ->
      $(colpkr).fadeIn(500)
      return false
    onHide: (colpkr) ->
      $(colpkr).fadeOut(500)
      _drawer.redraw()
      return false
    onChange: (hsb, hex, rgb) ->
      $("#colorSelector2 div").css('background-color', "##{hex}")
      _drawer.anotherColor = "##{hex}"
  }

  $("#selectorMain").change ()->
    _drawer.main = parseInt($("#selectorMain").val())
    _drawer.redraw()

  $("#selectorSub").change ()->
    _drawer.sub = parseInt($("#selectorSub").val())
    _drawer.redraw()

  $("#dropBox").popover {
    title: "Change background"
    content: "You can change background image to drag some image from you local PC and drop it to this area."
  }

  $("#colorSelector").popover {
    title: "Change main color"
    content: "You can change main color. The main color is circles' color in this canvas now."
  }

  $("#colorSelector2").popover {
    title: "Change sub color"
    content: "You can change sub color. The sub color is spade' color in this canvas now."
  }

  $("#selectorMain").popover {
    title: "Change main shape"
    content: "You can change main shape. The main shape is 'Circle' in this canvas now.<br />&#9824; I recommend you use black for spade shape &#9824;"
    placement: 'left'
  }

  $("#selectorSub").popover {
    title: "Change sub shape"
    content: "You can change sub shape. The sub shape is 'Spade' in this canvas now.<br />&#9824; I recommend you use black for spade shape &#9824;"
    placement: 'left'
  }

  $("#btnSave").popover {
    title: "Save as Image"
    content: "Canvas will be convert to JPEG image and I'll open it in a new window."
    placement: 'left'
  }

  document.getElementById("dropBox").addEventListener 'dragover',(event) ->
    event.preventDefault()

  document.getElementById("dropBox").addEventListener 'drop',(event) ->
    event.preventDefault()
    files = event.dataTransfer.files
    f = null
    for file in files
      f = file
    reader = new FileReader
    reader.onload = () ->
      imageData = reader.result
      _drawer.bgImage = imageData
      _drawer.redraw()
    reader.readAsDataURL(f)

  $("#btnSave").on 'click', () ->
    img = _g.canvas.toDataURL("image/jpeg", 1.0)
    window.open(img, "_blank")

なんかこういう画像が作れます。
screenshot

cover sample 001

 
 
screenshot

cover sample 002

 
 
screenshot

cover sample 003