Our Vision - Songbird

Current media players such as iTunes, Windows Media Player and Zune are proprietary, single‐vendor, vertically‐integrated silos, excluding any competitive services and devices. If Web browsers had been designed like current media players, Internet Explorer would surf only Microsoft.com and run only on the Xbox.

In contrast, Songbird is a media player that is as open as a Web browser. We’re building the open source, open standards, open Web user‐agent for digital media.

Our vision comes from Tim Berners‐Lee’s original vision of the Web:

“Keeping the medium and the content separate is a good rule in most media. When I turn on the television, I don't expect it to deliberately jump to a particular channel... I expect my television to be an impartial box. I also expect the same neutrality of software…”


We believe that Songbird dramatically lowers the barriers to entry for new services and devices, and will augur a vast, diverse and innovative Media Web. We aspire to make Songbird as ubiquitous as the Web browser, television set, or home stereo.
Songbird Owner’s Manual(PDF)

MP3のID3タグ解析APIと解析結果リスト検索クライアントを作った

元はというとyoupyさんが作ったPage to Podcastというページ内のmp3リンクからpodcast feedを生成するPipesでリンク文字をタイトルにしてもいいけどiTunesで見たときにわかりにくいから曲のタイトルをできるだけ正しくつけたいよねーという話があって、昔はtryntでID3解析APIがあったらしいけど今は動いてないので作りました。できるだけ早く応答返したいので先頭5000byteだけダウンロードして解析できたものだけそれらしい応答を返すようにしていてID3v1には未対応。他にもこのAPI使ってるPipesの例やpythonのサンプルコード載せてるけどAPI自体の他の使い道はあまり無いと思う。

APIで解析できたものはgoogle baseに登録してあるので誰でも検索できる。google baseの検索結果json使って検索画面を作ってる。今っぽいキーバインドでのページング(hjkl)とflashのmp3プレイヤー(a:開始, s:停止)が入ってるので早く試聴できたり、画面遷移はjs実装だけどurl解析してるので検索結果urlが再現できるようにしてます。検索画面のほかではキーワード検索podcast生成pipesでもgoogle baseのfeedを参照してpodcast向けに整形したfeedを生成してる。

やってることは単純なんだけどAPIだけ提供してその結果を間接的に第三者が勝手に使えるのはおもしろいかなーと思います。

Generate sound with data bending

Music by C. HUTCHINS » Blog Archive » Headerless Data No.1
Music by C. HUTCHINS » Blog Archive » Virtual Memory (Excerpt)
AIFFファイルのヘッダに適当なデータ(psdファイルとか)をくっつけて再生する、というのをyoupyさんから教えてもらってそれをWAVでやってみた。適当なWAVヘッダにBMPデータくっつけて再生してみたんだけどBMPだと構造が単純なので用意した画像から音がある程度想像できるようになるかもしれない。

WAVヘッダはWave File Format - The Sonic Spotを見ながら適当なファイルから適当にコピペしたのを使っている。

#include <stdio.h>

#define SZ_WAV_HEADER (4 * 10 + 3)
unsigned long* wav_header[] = {
    0x46464952, 0x00a17fe4, 0x45564157, 0x20746d66,
    0x00000010, 0x00020001, 0x0000ac44, 0x0002b110,
    0x00100004, 0x61746164, 0x00a17fc0
};

int main(int argc, char* argv[]) {
    char* filename = argv[1];
    FILE* fp_in;
    int size_in;
    FILE* fp_out;
    unsigned char* buff;
    char out_filename[256];

    fp_in = fopen(filename, "rb");
    fseek(fp_in, 0L, SEEK_END);
    size_in = ftell(fp_in);
    fseek(fp_in, 0L, SEEK_SET);

    buff = malloc(sizeof(unsigned char) * (size_in + SZ_WAV_HEADER));
    memcpy((void*)buff, (void*)wav_header, SZ_WAV_HEADER);
    fread(&buff[SZ_WAV_HEADER], size_in, sizeof(unsigned char), fp_in);
    fclose(fp_in);

    strcpy(out_filename, filename);
    strcat(out_filename, ".out.wav");
    fp_out = fopen(out_filename, "wb");
    fwrite(buff, size_in + SZ_WAV_HEADER, sizeof(unsigned char), fp_out);
    fclose(fp_out);
    free(buff);

    return 0;
}

lameなんかのmp3エンコーダにそのまま適当なデータつっこむ方法もあるみたいです(via kkshow)
.xxx-aiff -Box.net - Free Online File Storage, Internet File Sharing, Online Storage, Access Documents & Files Anywhere, Backup Data, Send Files

きょうのPipes 全文配信

Pipes: Ameblo Content All
Pipes: PingMag Japan - Fulltext
ニュースのRSSを全文配信で読む - ytsuchiyamaの日記
old style d.hatena EFT
Pipes: 全文配信化API

最後のPipesはURL/開始/終了位置を指定できるのでいろんなページに対応できます。

javascriptでURLパラメータを取得する

http://exampl.com/search.html#q=foobar;page=10みたいなurlの#以降を解析して{q:foobar, page:10}なオブジェクトとして返す。昔Yahoo!USの検索API使ってjavascriptだけではてブ検索作ったときに使っていた。

/*
 *
 * UrlParams
 *

 SYNOPSIS:

 window.onload = function() {
   var params = UrlParams.parse();
   var result = document.getElementById("result");
   var text = "";
   for (p in params) {
     text += "{" + p + ": " + params[p] + "}, ";
   }
   result.innerHTML = text;
   UrlParams.update("this_file.html", {a: 1, b: 2});
 }
*/
UrlParams = {
  separator: {
    params: ";",
    keyvalue: "="
  },
  
  parse: function() {
    var parts = location.href.split("#");
    var params = {};
    if (parts.length == 2) {
      var values = parts[1].split(this.separator.params);
      var kv;
      for (var i = 0, length = values.length; i < length; i++) {
        kv = values[i].split(this.separator.keyvalue);
        if (kv.length == 2) {
          params["" + kv[0]] = "" + decodeURI(kv[1]);
          log("  " + kv[0] + ": " + decodeURI(kv[1]));
        }
      }
    }
    return params;
  },
  
  update: function(file_name, params) {
    var parts = location.href.split("#");
    var concated_params = "#";
    if (parts[0].match(file_name)) {
      for (var p in params) {
        concated_params += p +
                           this.separator.keyvalue +
                           encodeURI(params[p]) +
                           this.separator.params;
      }
      location.href = parts[0] + concated_params;
    }
  }
};

好きなエディタでこまめに結果確認しながらjruby + processingする

jirbインタラクティブに書き換えるのも楽しいけどある程度構造考えながらだとエディタで編集しながらのほうがよくて、でも普段emacs使っていてprocessingのIDEは使いたくなかったのでfcwrap(現rascut)からファイル監視の処理をパクってかつ、いつも書く起動まわりの処理をまとめてみた。

require 'pathname'
require 'logger'
require 'thread'
require 'java'
include_class 'processing.core.PApplet'
include_class 'processing.core.PMatrix'
include_class "processing.core.PImage"
include_class "processing.core.PFont"

class SketchBook
  JFrame = javax.swing.JFrame
  DEFAULT_OPTIONS = {
    :interval => 2,
    :target_file => Time.now,
  }

  def initialize(filename, options = {})
    @options = DEFAULT_OPTIONS.merge(options)
    @target_file = Pathname.new(filename)
    @timestamp = Time.now
    @logger = Logger.new(STDOUT)
    @logger.progname = 'SketchBook'
    create_sketch
    open_sketch Sketch.new
  end

  def logger
    @logger
  end
  
  def create_sketch
    p5_program = ''
    @mutex = Mutex.new
    @mutex.synchronize do
      file = open(@target_file)
      file.each do |line|
        p5_program += line
      end
    end
  
    p5_program = [
      'class Sketch < PApplet',
       p5_program,
      'end'
    ].join("\n")

    def p5_environment
      binding
    end

    result = false
    begin
      eval(p5_program, p5_environment)
      result = true
    rescue
      logger.error "#{@target_file.basename.to_s} has SyntaxError!"
      result = false
    end
    result
  end
  
  def open_sketch(applet)
    @frame.visible = false if @frame
    @frame = JFrame.new applet.class.to_s
    @frame.content_pane.add applet
    @frame.default_close_operation = JFrame::EXIT_ON_CLOSE
    applet.init
    @frame.pack
    @frame.visible = true
  end
  
  def file_updated?
    if @target_file.mtime > @timestamp
      @timestamp = @target_file.mtime
      logger.info "#{@target_file.basename.to_s} was updated."
      true
    else
      false
    end
  end
  
  def run
    loop do
      if file_updated?
        if create_sketch
          open_sketch Sketch.new
        end
      end
      sleep [@options[:interval], 1].max
    end
  end
end

sketchbook = SketchBook.new(ARGV[0])
sketchbook.run

コマンドラインから以下のようにするとfoo.rbが監視対象になるので好きなエディタで編集できる。

$ jruby sketchbook.rb foo.rb

foo.rbはこんな感じでrubyであるという点以外は普通のprocessingに近く書ける

def setup
  size 100, 100
  background 255
  no_stroke
  @img_megane = load_image("utamaru160_meagne_only.PNG")
  smooth
end

def draw
  fill 255, random(100), random(100)
#  background 128
  4.times do
    tint 255, 255, 255, 30
    sz = random(20, 40)
#    image(@img_megane, random(width), random(height), sz, sz)
    ellipse(random(width), random(height), sz, sz)
  end
end


デモ映像ではwindowsコマンドラインからjruby + sketchbook.rb起動して同じマシン上のcoLinux内のemacsで編集してるfoo.rbを編集して保存されるたびにapplet窓にfoo.rbの内容が反映されている。PAppletのstaticメソッドなんかはあいかわらずPApplet.radiansとかアクセスしないとダメなんだけど普段使ってるエディタが使えるので大部楽しい。