FC2ブログ

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

GO FOR IT の 旋律に隠された特徴 に挑戦する

現在(2012年2月9日時点),Sony は新卒採用広報活動の一環として GO FOR IT というプログラムコンテストを実施しています.私自身も就職活動中の学生ですので,問題に挑戦することにしました.挑戦する問題は第4問の,旋律に隠された特徴です.

Keywords: 旋律, 特徴量, 作曲, GO FOR IT


問題の要点は,旋律の特徴量演算器と生成器の作成です.
(i) まずA,B,C3つの旋律が与えられ,AとBの特徴量は異なり,AとCの特徴量は等しいという拘束を満たす演算器をコーディングします.
(ii) 次に新たな旋律D,E,Fが与えられ,Bと同じ特徴量を示す旋律を回答します.
(iii) 最後に旋律生成器をコーディングします.満たすべき旋律の条件は,

・与えられた旋律Gと同じ特徴量を持つこと
・譜面上の位置-2ではじまり同じ音で終わること
・旋律の長さは全音符3つより長く4つ分まで

となります.


演算器のアルゴリズムを説明します.まず連続する音符について音階の変化と音符の長さから微分量を計算し和をとります.次に休符を係数とみなし,直前に得られた微分量の和と積を取ります.このようにして旋律を ax + by + ... の形とみなし,特徴量を計算します.

生成器のアルゴリズムでは,まず乱数により基本となる旋律を与えます.これを旋律操作器に読み込ませます.操作器では

・音階を1上げる,または下げる
・ある音符を半分の長さの2つの音符に分ける
・ある2つの音符を1つの音符に結合する

の3つの操作を旋律に対して施します.旋律の特徴量が目標量に一致するまで操作を反復し,一致した時点で終了します.実際には十分な変動を与えるため,最低反復回数以下では終了しないように条件付けをしています.


プログラムは2つに分かれています.操作関数やコンポーネントは music.py に,それらを利用して解を求める部分は music_demo.py に書きました.

<作業環境>
Windows7 Professional
Python 2.6.6

<必要実行環境>
Python 2.6.x

<実行方法>
music.py と music_demo.py を同じディレクトリに配置し,端末から以下のコマンドを実行してください.
python music_demo.py
私の環境では以下の出力が得られました.
(i) A != B, A == C
11 <-- A
13 <-- B
11 <-- C

(ii) Which one's feature is same with B
14 <-- D
34 <-- E
13 <-- F

(iii) Create melody has same feature with G
16 <-- G
16 <-- Original
-2:4:,3:4:,-2:4.:,0:8:,3:4:,-5:4:,2:2:,:8:,:8:,-1:8:,-1:8:,0:4:,2:4:,-2:2.:,1:8:,-1:8:,-2:8:
以上より(ii),(iii)の回答は以下のようになります.
(ii) F
(iii) -2:4:,3:4:,-2:4.:,0:8:,3:4:,-5:4:,2:2:,:8:,:8:,-1:8:,
-1:8:,0:4:,2:4:,-2:2.:,1:8:,-1:8:,-2:8:


最後にソースコードを示します.今回のソースコードは MIT ライセンスとします.

music.py
"""Copyright (c) 2012 Akihiko Ishida, http://codeit.blog.fc2.com/

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

MIN_LEVEL = -6.0
MAX_LEVEL = 3.0

DURATIONS = [1.0, 2.0, 4.0, 8.0, 16.0, 32.0]


class Note(object):

def __init__(self, level=0, duration=1, notation=0, applied=0):
self.level = float(level)
self.duration = float(duration)
self.notation = float(notation)
self.applied = applied

def __str__(self):
level = str(int(self.level))

duration = 4 / self.duration
if duration in DURATIONS:
duration = str(int(duration))
else:
duration = 4 / (self.duration * 2.0 / 3.0)
if duration in DURATIONS:
duration = str(int(duration)) + '.'
else:
raise ValueError, 'unavailable duration'

n = self.notation
if n == 0.0:
notation = ''
elif n == -1.0:
notation = 'd'
elif n == -0.5:
notation = 'b'
elif n == 0.5:
notation = 's'
elif n == 1.0:
notation = 'x'

return level + ':' + duration + ':' + notation

def copy(self):
return Note(self.level, self.duration, self.notation, self.applied)

@staticmethod
def fromstring(s):
toks = s.split(':')

x = float(toks[0])

y = float(toks[1])
if y not in DURATIONS:
raise ValueError, '[' + y + '] is not available'
if toks[1][-1] == '.':
y /= 1.5

if toks[2] == '':
z = 0.0
elif toks[2] == 'd':
z = -1.0
elif toks[2] == 'b':
z = -0.5
elif toks[2] == 's':
z = 0.5
elif toks[2] == 'x':
z = 1.0
else:
raise ValueError, '[' + toks[2] + '] is not acceptable'

return Note(x, 4 / y, z)


class NoteSequence(object):

def __init__(self, notes=[]):
self.notes = notes

def __str__(self):
s = ''
for note in self.notes:
s += str(note)
s += ','
return s[:-1]

def level(self, indices, func):
for i in get_sorted_indices(indices):
n = self.notes[i]
n.level = func(n.level)
if n.level > MAX_LEVEL: n.level = MAX_LEVEL
elif n.level < MIN_LEVEL: n.level = MIN_LEVEL
n.applied += 1

def split(self, indices):
for i in get_sorted_indices(indices):
n = self.notes[i]
n.duration /= 2.0
n.applied += 1
self.notes.insert(i, n)

def merge(self, indices):
for i in get_sorted_indices(indices):
i0 = i
i1 = i + 1

self.notes[i0].applied += 1

if i1 < len(self.notes):
n0 = self.notes[i0]
n1 = self.notes[i1]

duration = n0.duration + n1.duration
level = n0.level * n0.duration + n1.level * n1.duration
level /= duration

n0.level = round(level)
n0.duration = duration

self.notes.remove(n1)

def copy(self):
return NoteSequence([x.copy() for x in self.notes])

def eval(self):
src = self.notes

set0 = src[ :-1]
set1 = src[1:]

r = 0.0
for n0, n1 in zip(set0, set1):
dx = n0.duration
dy = (n1.level + n1.notation) - (n0.level + n0.notation)
r += abs(dy) / dx

return r


class Rest(object):

def __init__(self, duration=1.0):
self.duration = duration

def __str__(self):
duration = str(int(4 / self.duration))
return ':' + duration + ':'

@staticmethod
def fromstring(s):
toks = s.split(':')

duration = float(toks[1])
if duration not in DURATIONS:
raise ValueError, '[' + duration + '] is not available'

return Rest(4 / duration)


class Melody(object):

def __init__(self, items=[]):
self.items = items

def __str__(self):
s = ''
for item in self.items:
s += str(item)
s += ','
return s[:-1]

def eval(self):
ret = 0.0

scale = 0.0
for item in reversed(self.items):
if isinstance(item, NoteSequence):
ret += scale * item.eval()
elif isinstance(item, Rest):
scale = item.duration

return int(round(ret))

@staticmethod
def fromstring(s):
notes, items = [], []
for toks in s.split(','):
if toks[0] == ':': # Rest
items.append(NoteSequence(notes))
items.append(Rest.fromstring(toks))
notes = []
else: # Note
notes.append(Note.fromstring(toks))

if len(notes) > 0: items.append(notes)

return Melody(items)


def get_sorted_indices(indices):
if isinstance(indices, int): indices = [indices,]
indices.sort(reverse=True)
return indices


def get_prospect(seq, index, feature=32.0):
# case 1: increase level
c1 = seq.copy()
c1.level(index, lambda x: x + 1)

# case 2: decrease level
c2 = seq.copy()
c2.level(index, lambda x: x - 1)

# case 3: split note
c3 = seq.copy()
c3.split(index)

# case 4: merge notes
c4 = seq.copy()
c4.merge(index)

func = lambda x, y: cmp(abs(feature - x.eval()), abs(feature - y.eval()))

result = [c1, c2, c3, c4]
result.sort(func)
return result[0]


def get_index(seq):
nmin = seq.notes[0]
for n in seq.notes:
if nmin.applied > n.applied: nmin = n
return seq.notes.index(nmin)


def create_sequence(src, miniter=20, maxiter=200, feature=32.0):
seq = src

i = 0
while i < miniter:
seq = get_prospect(seq, get_index(seq), feature)
i += 1
while i < maxiter and round(seq.eval()) != feature:
seq = get_prospect(seq, get_index(seq), feature)
i += 1

return seq

music_demo.py
"""Copyright (c) 2012 Akihiko Ishida, http://codeit.blog.fc2.com/

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

from random import seed, randint
from music import Note, NoteSequence, Rest, Melody,\
create_sequence, MIN_LEVEL, MAX_LEVEL

melody_a = '\
-3:4:,-2:4:,-1:4:,0:4:b,-1:4:,-2:4:,-3:4:,:4:,\
-1:4:,0:4:b,1:4:,2:4:,1:4:,0:4:b,-1:4:,:4:'
melody_b = '\
3:4:,2:4:,1:4:,0:4:b,1:4:,2:4:,3:4:,:4:,1:4:,\
0:4:b,-1:4:,-2:4:,-1:4:,0:4:b,1:4:,:4:'
melody_c = '\
3:4:,2:4:,1:4:,0:4:,1:4:,2:4:,3:4:,:4:,1:8:,\
0:4.:,-1:4:,-2:4:,:8:,-1:4.:,0:8:,1:8:,:4:'
melody_d = '\
-6:8:,-6:8:,-6:8:,-4:8:,-2:8:,-2:8:,-2:8:,:8:,\
-5:8:,-5:8:,-5:8:,-3:8:,-2:8:,-2:8:,-2:8:,:8:,:8:,\
-2:8:,-2:8:,-1:8:b,-1:8:,-1:8:,-1:8:,-1:8:b,-2:4:,0:4:,1:4:,:4:'
melody_e = '\
-6:8.:,-7:16:,-6:8:,-5:8:,-4:8:,-4:8:,-4:4:,-3:8.:,\
-4:16:,-5:8:,-6:8:,-5:4:,:4:,-3:8:,-5:4:,-5:8:,-4:8:,\
-4:8:,-3:8:,-3:8:,-4:8:,-4:8:,-5:8:,-5:8:,-6:4:,:4:'
melody_f = '\
-6:2:,-5:4:,-6:8:,-5:8:,-4:4:,-2:4:,-4:4:,:4:,-5:4:,\
-5:4:,-6:4:,-5:4:,-4:2.:,:4:'
melody_g = '\
-2:8.:,-1:16:,-2:8.:,-1:16:,-2:4:,-4:4:,-4:8.:,-3:16:,\
-4:8.:,-3:16:,-4:4:,-6:4:,-4:8:,:8:,-6:8.:,-5:16:,-4:8:,:8:,\
-6:8.:,-5:16:,-4:8.:,-4:16:,-2:8.:,-2:16:,-5:8.:,-4:16:,-5:4:'

def main():
print '(i) A != B, A == C'
print Melody.fromstring(melody_a).eval(), '<-- A'
print Melody.fromstring(melody_b).eval(), '<-- B'
print Melody.fromstring(melody_c).eval(), '<-- C'
print

print '(ii) Which one\'s feature is same with B'
print Melody.fromstring(melody_d).eval(), '<-- D'
print Melody.fromstring(melody_e).eval(), '<-- E'
print Melody.fromstring(melody_f).eval(), '<-- F'
print

print '(iii) Create melody has same feature with G'
print Melody.fromstring(melody_g).eval(), '<-- G'

seed(20120206)

mylevel = lambda: float(randint(MIN_LEVEL, MAX_LEVEL))

notes0 = [
Note(-2, applied=10000), # not edited in creating process
Note(mylevel()), Note(mylevel()), Note(mylevel()), Note(mylevel()),
Note(mylevel()), Note(mylevel()), Note(mylevel())
]
seq0 = create_sequence(NoteSequence(notes0), feature=32.0)

notes1 = [
Note(-2, 0.5, applied=10000), # not edited in creating process
Note(mylevel()), Note(mylevel()), Note(mylevel()), Note(mylevel()),
Note(mylevel()), Note(mylevel()), Note(mylevel())
]
seq1 = create_sequence(NoteSequence(notes1), feature=16.0)
seq1.notes = seq1.notes[::-1]

melody = Melody([seq0, Rest(0.5), Rest(0.5), seq1])

print melody.eval(), '<-- Original'
print melody

if __name__ == '__main__':
main()
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

プロフィール

Ishida Akihiko

Author:Ishida Akihiko
FC2ブログへようこそ!

免責事項
当サイトに掲載する記事内容は,必ずしも正確性,信頼性,妥当性,有用性,完成度などを保証しません.記事の利用はすべて自己責任でお願いします.当サイトに掲載された内容によって発生したいかなる損害に対しても,管理人は一切の責任を負いかねます.
最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
アクセスカウンター
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。