(function(){
'use strict';
/* ============================================
定数・データ
============================================ */
var MIDI_START = 36; // C2
var MIDI_END = 84; // C6
var NOTE_NAMES = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];
var MALE_TALK_LOW = 40; // E2
var MALE_TALK_HIGH = 53; // F3
var FEMALE_TALK_LOW = 55; // G3
var FEMALE_TALK_HIGH = 64; // E4
function midiToName(m){
return NOTE_NAMES[m%12]+(Math.floor(m/12)-1);
}
function midiToFreq(m){
return 440*Math.pow(2,(m-69)/12);
}
// DAM Top 100 [rank, title, artist, lowMidi, chestMidi, falsettoMidi(0=none), suitability]
var SONGS=[
[1,"ライラック","Mrs. GREEN APPLE",50,71,73,"男性"],
[2,"怪獣の花唄","Vaundy",50,71,74,"高難度"],
[3,"残酷な天使のテーゼ","高橋洋子",58,72,0,"女性"],
[4,"サウダージ","ポルノグラフィティ",59,68,0,"女性"],
[5,"マリーゴールド","あいみょん",54,71,0,"女性"],
[6,"さよならエレジー","菅田将暉",49,68,0,"男性"],
[7,"ドライフラワー","優里",55,69,72,"男性"],
[8,"かわいいだけじゃだめですか?","CUTIE STREET",59,76,75,"女性"],
[9,"ダーリン","Mrs. GREEN APPLE",46,73,69,"男性"],
[10,"小さな恋のうた","MONGOL800",46,68,0,"男性"],
[11,"水平線","back number",50,67,72,"男性"],
[12,"ケセラセラ","Mrs. GREEN APPLE",49,73,77,"高難度"],
[13,"高嶺の花子さん","back number",52,71,74,"男性"],
[14,"奏(かなで)","スキマスイッチ",50,70,0,"男性"],
[15,"点描の唄","Mrs. GREEN APPLE",57,74,79,"女性"],
[16,"チェリー","スピッツ",52,69,0,"男性"],
[17,"366日","HY",52,77,0,"高難度"],
[18,"シングルベッド","シャ乱Q",49,68,0,"男性"],
[19,"晩餐歌","tuki.",56,73,78,"女性"],
[20,"青と夏","Mrs. GREEN APPLE",51,73,76,"高難度"],
[21,"ひまわりの約束","秦基博",51,70,70,"男性"],
[22,"丸の内サディスティック","椎名林檎",58,79,0,"女性"],
[23,"チャンカパーナ","NEWS",50,71,0,"男性"],
[25,"最後の雨","中西保志",47,69,0,"男性"],
[26,"Story","AI",55,72,0,"女性"],
[27,"Soranji","Mrs. GREEN APPLE",48,71,78,"高難度"],
[28,"ハナミズキ","一青窈",56,71,73,"女性"],
[29,"恋人ごっこ","マカロニえんぴつ",53,70,73,"男性"],
[30,"大阪LOVER","DREAMS COME TRUE",58,74,0,"女性"],
[31,"糸","中島みゆき",53,70,0,"男性"],
[32,"世界が終るまでは…","WANDS",52,69,0,"男性"],
[33,"愛をこめて花束を","Superfly",55,77,0,"女性"],
[34,"キセキ","GReeeeN",55,70,0,"高難度"],
[35,"Lemon","米津玄師",47,71,71,"男性"],
[36,"Bling-Bang-Bang-Born","Creepy Nuts",45,66,0,"男性"],
[37,"カブトムシ","aiko",56,72,77,"女性"],
[38,"幾億光年","Omoinotake",50,73,78,"高難度"],
[39,"アイノカタチ","MISIA",56,75,0,"女性"],
[40,"イケナイ太陽","ORANGE RANGE",50,72,0,"男性"],
[41,"クリスマスソング","back number",47,69,73,"高難度"],
[42,"島人ぬ宝","BEGIN",48,65,0,"男性"],
[43,"怪獣","サカナクション",54,70,71,"男性"],
[44,"Bunny Girl","AKASAKI",48,64,0,"男性"],
[45,"歌うたいのバラッド","斉藤和義",45,69,69,"男性"],
[46,"気まぐれロマンティック","いきものがかり",57,71,74,"女性"],
[47,"僕のこと","Mrs. GREEN APPLE",48,72,79,"男性"],
[48,"シャルル","バルーン",50,73,0,"男性"],
[49,"シンデレラボーイ","Saucy Dog",52,73,76,"高難度"],
[50,"花束のかわりにメロディーを","清水翔太",51,68,73,"男性"],
[51,"Pretender","Official髭男dism",51,72,73,"高難度"],
[52,"コイスルオトメ","いきものがかり",59,73,73,"女性"],
[53,"メロディー","玉置浩二",49,66,66,"男性"],
[54,"I LOVE YOU","尾崎豊",52,66,69,"男性"],
[55,"白い恋人達","桑田佳祐",50,67,71,"男性"],
[56,"3月9日","レミオロメン",48,67,0,"男性"],
[57,"チェックのワンピース","back number",51,68,0,"男性"],
[58,"アゲハ蝶","ポルノグラフィティ",55,68,0,"男性"],
[59,"IRIS OUT","米津玄師",44,69,71,"男性"],
[60,"ハレンチ","ちゃんみな",52,71,0,"男性"],
[61,"はいよろこんで","こっちのけんと",50,66,0,"男性"],
[62,"虹","菅田将暉",48,69,0,"男性"],
[63,"津軽海峡・冬景色","アンジェラ・アキ",56,75,0,"女性"],
[64,"インフェルノ","Mrs. GREEN APPLE",40,71,76,"男性"],
[65,"倍倍FIGHT!","CANDY TUNE",56,74,0,"女性"],
[66,"タッチ","岩崎良美",57,71,0,"女性"],
[67,"栄光の架橋","ゆず",47,69,0,"男性"],
[68,"クスシキ","Mrs. GREEN APPLE",47,69,77,"男性"],
[69,"アイドル","YOASOBI",54,74,78,"女性"],
[70,"First Love","宇多田ヒカル",52,75,77,"高難度"],
[71,"TSUNAMI","サザンオールスターズ",45,70,74,"男性"],
[72,"粉雪","レミオロメン",47,69,69,"男性"],
[73,"CITRUS","Da-iCE",53,73,74,"女性"],
[74,"HANABI","Mr.Children",44,69,0,"男性"],
[75,"雪の華","中島美嘉",54,73,75,"女性"],
[76,"わたしの一番かわいいところ","FRUITS ZIPPER",55,73,73,"女性"],
[77,"DAN DAN 心魅かれてく","FIELD OF VIEW",44,69,0,"男性"],
[78,"残響散歌","Aimer",56,73,75,"女性"],
[79,"猫","DISH//",48,70,0,"男性"],
[80,"花束","back number",45,70,72,"男性"],
[81,"ただ君に晴れ","ヨルシカ",50,75,0,"女性"],
[82,"裸の心","あいみょん",53,73,75,"女性"],
[83,"炎","LiSA",54,76,76,"女性"],
[84,"ヒロイン","back number",50,71,71,"男性"],
[85,"APT.","ROSÉ & Bruno Mars",58,72,70,"女性"],
[86,"ダンスホール","Mrs. GREEN APPLE",57,71,78,"女性"],
[87,"I wonder","Da-iCE",46,71,75,"男性"],
[88,"愛唄","GReeeeN",51,72,0,"高難度"],
[89,"未来予想図 Ⅱ","DREAMS COME TRUE",53,75,0,"女性"],
[90,"魔法の絨毯","川崎鷹也",49,68,0,"男性"],
[92,"天体観測","BUMP OF CHICKEN",44,68,0,"男性"],
[93,"時の流れに身をまかせ","テレサ・テン",55,70,74,"女性"],
[94,"眠り姫","SEKAI NO OWARI",50,65,0,"男性"],
[95,"紅蓮華","LiSA",52,74,79,"高難度"],
[96,"Love so sweet","嵐",51,69,0,"男性"],
[97,"ビリミリオン","優里",50,70,72,"男性"],
[98,"115万キロのフィルム","Official髭男dism",46,72,0,"高難度"],
[99,"ロビンソン","スピッツ",54,71,73,"女性"],
[100,"真夏の果実","サザンオールスターズ",45,69,71,"男性"]
];
// DAM ボカロランキング [rank, title, artist, lowMidi, highMidi, 0(no falsetto), ""]
// 出典: merged-data0317.csv (50曲) + Web調査 (50曲)
var SONGS_VOCALOID=[
[1,"シャルル","バルーン",49,82,0,""],
[2,"モニタリング","DECO*27",69,77,0,""],
[3,"少女レイ","みきとP",69,74,0,""],
[4,"酔いどれ知らず","Kanaria",54,76,0,""],
[5,"千本桜","WhiteFlame feat.初音ミク",55,76,0,""],
[6,"テトリス","柊マグネタイト",71,78,0,""],
[7,"ヒバナ","DECO*27",71,74,0,""],
[8,"KING","Kanaria",55,79,0,""],
[9,"ロキ","みきとP",71,76,0,""],
[10,"ロミオとシンデレラ","doriko feat.初音ミク",56,77,0,""],
[11,"天ノ弱","164 feat.GUMI",69,79,0,""],
[12,"アスノヨゾラ哨戒班","Orangestar feat.IA",52,78,0,""],
[13,"ブリキノダンス","日向電工",67,84,0,""],
[14,"命に嫌われている","カンザキイオリ",55,79,0,""],
[15,"からくりピエロ","40mP feat.初音ミク",53,81,0,""],
[16,"神っぽいな","ピノキオピー",69,75,0,""],
[17,"妄想感傷代償連盟","DECO*27",70,75,0,""],
[18,"恋愛裁判","40mP feat.初音ミク",51,68,0,""],
[19,"砂の惑星","ハチ feat.初音ミク",53,68,0,""],
[20,"ロウワー","ぬゆり",61,79,0,""],
[21,"ヴァンパイア","DECO*27",71,80,0,""],
[22,"ロストワンの号哭","Neru feat.鏡音リン",71,95,0,""],
[23,"右肩の蝶","のりぴー feat.鏡音レン",56,77,0,""],
[24,"オーバーライド","吉田夜世",56,73,0,""],
[25,"高音厨音域テスト","木村わいP feat.初音ミク",69,80,0,""],
[26,"夜もすがら君想ふ","TOKOTOKO(西沢さんP) feat.GUMI",56,73,0,""],
[27,"夜明けと蛍","n-buna feat.初音ミク",57,68,0,""],
[28,"脳漿炸裂ガール","れるりり feat.初音ミク&GUMI",60,75,0,""],
[29,"ラヴィ","すりぃ",55,95,0,""],
[30,"ゴーストルール","DECO*27",69,78,0,""],
[31,"抜錨","ナナホシ管弦楽団 feat.巡音ルカ",55,94,0,""],
[32,"テレパシ","DECO*27",55,74,0,""],
[33,"エゴロック","すりぃ feat.鏡音レン",55,79,0,""],
[34,"乙女解剖","DECO*27",71,85,0,""],
[35,"サマータイムレコード","じん(自然の敵P) feat.IA",52,78,0,""],
[36,"テレキャスタービーボーイ","すりぃ feat.鏡音レン",63,80,0,""],
[37,"メランコリック","Junky feat.鏡音リン",60,75,0,""],
[38,"ヴィラン","てにをは feat.flower",69,73,0,""],
[39,"地球最後の告白を","kemu feat.GUMI",53,79,0,""],
[40,"六兆年と一夜物語","kemu feat.IA",69,78,0,""],
[41,"メルト","supercell feat.初音ミク",69,93,0,""],
[42,"マトリョシカ","ハチ feat.初音ミク&GUMI",61,76,0,""],
[43,"グッバイ宣言","Chinozo",60,84,0,""],
[44,"ダイダイダイダイダイキライ","雨良 Amala",70,77,0,""],
[45,"だれかの心臓になれたなら","ユリイ・カノン",70,84,0,""],
[46,"強風オールバック","ゆこぴ",55,83,0,""],
[47,"ハッピーシンセサイザ","EasyPop feat.巡音ルカ&GUMI",55,68,0,""],
[48,"回る空うさぎ","Orangestar",49,77,0,""],
[49,"神のまにまに","れるりり feat.初音ミク&鏡音リン&GUMI",56,75,0,""],
[50,"アンノウン・マザーグース","wowaka",42,81,0,""],
[51,"ルカルカ★ナイトフィーバー","SAM(samfree)",63,75,0,""],
[52,"とても素敵な六月でした","Eight feat.初音ミク",62,82,0,""],
[53,"初音ミクの消失","cosMo@暴走P feat.初音ミク",70,80,0,""],
[54,"ノンブレス・オブリージュ","ピノキオピー",69,84,0,""],
[55,"D/N/A","Azari",62,76,0,""],
[56,"雨とペトラ","バルーン",53,82,0,""],
[57,"ベノム","かいりきベア",61,80,0,""],
[58,"コールボーイ","syudou",64,80,0,""],
[59,"吉原ラメント","亜沙 feat.重音テト",53,83,0,""],
[60,"ジャンキーナイトタウンオーケストラ","すりぃ",69,77,0,""],
[61,"太陽系デスコ","ナユタン星人",60,84,0,""],
[62,"いーあるふぁんくらぶ","ミキト(みきとP) feat.GUMI&鏡音リン",70,72,0,""],
[63,"エンヴィーベイビー","Kanaria",48,68,0,""],
[64,"メズマライザー","サツキ",56,86,0,""],
[65,"デリヘル呼んだら君が来た","ナナホシ管弦楽団 feat.初音ミク&IA",60,74,0,""],
[66,"のだ","大漠波新",55,75,0,""],
[67,"DAYBREAK FRONTLINE","Orangestar",55,93,0,""],
[68,"東京テディベア","Neru feat.鏡音リン",62,94,0,""],
[69,"ローリンガール","wowaka feat.初音ミク",61,78,0,""],
[70,"悪魔の踊り方","キタニタツヤ",58,67,0,""],
[71,"モザイクロール","DECO*27",71,75,0,""],
[72,"パンダヒーロー","ハチ feat.GUMI",71,79,0,""],
[73,"あの夏が飽和する","カンザキイオリ",62,93,0,""],
[74,"ウミユリ海底譚","n-buna feat.初音ミク",55,76,0,""],
[75,"あなたの夜が明けるまで","傘村トータ",62,95,0,""],
[76,"ワールドイズマイン","supercell feat.初音ミク",56,75,0,""],
[77,"カゲロウデイズ","じん(自然の敵P) feat.初音ミク",71,77,0,""],
[78,"独りんぼエンヴィー","koyori(電ポルP) feat.初音ミク",69,75,0,""],
[79,"フィクサー","ぬゆり feat.flower",66,85,0,""],
[80,"メーベル","バルーン",51,73,0,""],
[81,"ダーリンダンス","かいりきベア",54,72,0,""],
[82,"心做し","蝶々P feat.GUMI",48,81,0,""],
[83,"混沌ブギ","jon-YAKITORY",55,80,0,""],
[84,"弱虫モンブラン","DECO*27",69,72,0,""],
[85,"花瓶に触れた","バルーン",67,95,0,""],
[86,"チェリーポップ","DECO*27",69,75,0,""],
[87,"Surges","Orangestar feat.IA&初音ミク",53,93,0,""],
[88,"エイリアンエイリアン","ナユタン星人",60,84,0,""],
[89,"ジェヘナ","wotaku",69,80,0,""],
[90,"ジレンマ","DECO*27",57,68,0,""],
[91,"化けの花","なきそ",62,72,0,""],
[92,"いますぐ輪廻","なきそ",55,78,0,""],
[93,"Blessing","halyosy",55,78,0,""],
[94,"転生林檎","ピノキオピー",60,75,0,""],
[95,"Calc.","ジミーサムP feat.初音ミク",54,78,0,""],
[96,"夜咄ディセイブ","じん(自然の敵P) feat.IA",69,74,0,""],
[97,"ビターチョコデコレーション","syudou",69,74,0,""],
[98,"深海少女","ゆうゆ feat.初音ミク",60,78,0,""],
[99,"帝国少女","R Sound Design",59,66,0,""],
[100,"妄想税","DECO*27",64,75,0,""]
];
function getActiveSongs(){ return state.songMode === 'vocaloid' ? SONGS_VOCALOID : SONGS; }
/* ============================================
状態管理
============================================ */
var state = {
currentNote: null, // 最後にタップした鍵盤のMIDI番号
lowest: null, // MIDI number
chest: null,
falsetto: null,
diagnosed: false,
showCount: 5, // 表示曲数
filter: 'all',
searchQuery: '',
gender: null,
songMode: 'normal', // 'normal' | 'vocaloid'
adjustments: [], // 計算済みキー調整
adjustmentsVocal: [] // ボカロ用
};
/* ============================================
Web Audio API - ピアノ音源
============================================ */
var audioCtx = null;
function initAudio(){
if(!audioCtx){
audioCtx = new (window.AudioContext||window.webkitAudioContext)();
}
if(audioCtx.state==='suspended') audioCtx.resume();
}
function playNote(midi){
initAudio();
var freq = midiToFreq(midi);
var now = audioCtx.currentTime;
// Piano-like synthesis: fundamental + harmonics with decay
var harmonics = [1, 2, 3, 4, 5, 6];
var amps = [1, 0.5, 0.3, 0.15, 0.08, 0.04];
var gain = audioCtx.createGain();
gain.connect(audioCtx.destination);
// Higher notes decay faster
var decayTime = Math.max(0.3, 1.5 - (midi - 36) * 0.02);
gain.gain.setValueAtTime(0, now);
gain.gain.linearRampToValueAtTime(0.25, now + 0.008);
gain.gain.exponentialRampToValueAtTime(0.001, now + decayTime);
harmonics.forEach(function(h, i){
var osc = audioCtx.createOscillator();
var hGain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq * h, now);
hGain.gain.setValueAtTime(amps[i], now);
osc.connect(hGain);
hGain.connect(gain);
osc.start(now);
osc.stop(now + decayTime + 0.05);
});
}
/* ============================================
ピアノ鍵盤の構築
============================================ */
function buildPiano(){
var container = document.getElementById('vrc-piano');
if(!container) return;
container.innerHTML = '';
for(var m = MIDI_START; m <= MIDI_END; m++){
var noteIdx = m % 12;
var isBlack = [1,3,6,8,10].indexOf(noteIdx) !== -1;
var key = document.createElement('div');
key.className = isBlack ? 'vrc-key-black' : 'vrc-key-white';
key.dataset.midi = m;
if(!isBlack){
var name = midiToName(m);
// Show note name on white keys at C and at specific notes
if(noteIdx === 0 || noteIdx === 4 || noteIdx === 7){
key.textContent = name;
}
}
key.addEventListener('pointerdown', function(e){
e.preventDefault();
var mid = parseInt(this.dataset.midi);
onKeyPress(mid, this);
});
container.appendChild(key);
}
}
function onKeyPress(midi, el){
playNote(midi);
// Remove previous current highlight
var prev = document.querySelector('#vrc-piano .current');
if(prev) prev.classList.remove('current');
// Persist highlight on tapped key
el.classList.add('current');
state.currentNote = midi;
// Update note display
updateNoteDisplay(midi);
}
/* ============================================
音名表示
============================================ */
function updateNoteDisplay(midi){
var display = document.getElementById('vrc-note-display');
var name = midiToName(midi);
var hint = '';
if(midi >= MALE_TALK_LOW && midi <= MALE_TALK_HIGH){
hint = '=男性の話し声の高さ';
} else if(midi >= FEMALE_TALK_LOW && midi <= FEMALE_TALK_HIGH){
hint = '=女性の話し声の高さ';
}
display.innerHTML = '' + name + '' +
(hint ? '' + hint + '' : '');
}
/* ============================================
音域設定
============================================ */
function setRangeValue(mode, midi){
state[mode] = midi;
var name = midiToName(midi);
var valEl = document.getElementById('vrc-val-' + mode);
valEl.textContent = name;
var btn = document.getElementById('vrc-btn-' + mode);
btn.classList.add('has-value');
// Validation
if(state.lowest !== null && state.chest !== null && state.chest < state.lowest){
state.chest = null;
document.getElementById('vrc-val-chest').textContent = '';
document.getElementById('vrc-btn-chest').classList.remove('has-value');
}
if(state.chest !== null && state.falsetto !== null && state.falsetto < state.chest){
// falsetto can be lower than chest in some cases, allow it
}
updatePianoHighlight();
checkDiagnoseReady();
}
function updatePianoHighlight(){
var keys = document.querySelectorAll('#vrc-piano > div');
keys.forEach(function(k){
k.classList.remove('in-range-chest','in-range-falsetto','is-lowest','is-chest','is-falsetto','current');
var m = parseInt(k.dataset.midi);
if(state.lowest !== null && m === state.lowest) k.classList.add('is-lowest');
if(state.chest !== null && m === state.chest) k.classList.add('is-chest');
if(state.falsetto !== null && m === state.falsetto) k.classList.add('is-falsetto');
if(state.lowest !== null && state.chest !== null){
if(m > state.lowest && m < state.chest) k.classList.add('in-range-chest');
}
if(state.chest !== null && state.falsetto !== null){
if(m > state.chest && m < state.falsetto) k.classList.add('in-range-falsetto');
}
});
}
function checkDiagnoseReady(){
var btn = document.getElementById('vrc-diagnose-btn');
if(state.lowest !== null && state.chest !== null && state.falsetto !== null){
btn.classList.add('show');
} else {
btn.classList.remove('show');
}
}
function resetAll(){
state.lowest = null;
state.chest = null;
state.falsetto = null;
state.currentNote = null;
state.diagnosed = false;
state.showCount = 5;
['lowest','chest','falsetto'].forEach(function(m){
document.getElementById('vrc-val-' + m).textContent = '';
var btn = document.getElementById('vrc-btn-' + m);
btn.classList.remove('has-value');
});
document.getElementById('vrc-diagnose-btn').classList.remove('show');
document.getElementById('vrc-results').classList.remove('show');
document.getElementById('vrc-songs').classList.remove('show');
document.getElementById('vrc-picks').classList.remove('show');
updatePianoHighlight();
var display = document.getElementById('vrc-note-display');
display.innerHTML = '鍵盤をタップして音を鳴らしてみましょう';
}
/* ============================================
診断 & キー調整計算
============================================ */
function diagnose(){
if(state.lowest===null||state.chest===null||state.falsetto===null) return;
state.diagnosed = true;
// Calculate key adjustments for a song list
function calcAll(songList){
return songList.map(function(s){
var shift = calcOptimalKey(
{low:s[3], chest:s[4], falsetto:s[5]||s[4]},
{low:state.lowest, chest:state.chest, falsetto:state.falsetto}
);
var songHigh0 = Math.max(s[4], s[5]||s[4]);
var userHigh0 = Math.max(state.chest, state.falsetto);
var ol0 = Math.max(0, Math.min(songHigh0, userHigh0) - Math.max(s[3], state.lowest));
var sw0 = songHigh0 - s[3];
var cov0 = sw0 > 0 ? ol0 / sw0 : 0;
return {
rank: s[0], title: s[1], artist: s[2],
low: s[3], chest: s[4], falsetto: s[5], suit: s[6],
shift: shift, absShift: Math.abs(shift), coverageAt0: cov0
};
});
}
state.adjustments = calcAll(SONGS);
state.adjustments.sort(function(a,b){ return a.rank - b.rank; });
state.adjustmentsVocal = calcAll(SONGS_VOCALOID);
state.adjustmentsVocal.sort(function(a,b){ return a.rank - b.rank; });
showResults();
showPicks();
showSongs();
document.getElementById('vrc-results').scrollIntoView({behavior:'smooth', block:'start'});
}
function calcOptimalKey(song, user){
var bestShift = 0;
var bestScore = -999;
var userHigh = Math.max(user.chest, user.falsetto);
for(var k = -12; k <= 12; k++){
var sLow = song.low + k;
var songHigh = Math.max(song.chest + k, (song.falsetto||song.chest) + k);
// High notes above user's total range = critical
var exceedAbove = Math.max(0, songHigh - userHigh);
// Low notes below user's range = shift up to fix
var exceedBelow = Math.max(0, user.low - sLow);
var penalty = exceedAbove * 0.5 + exceedBelow * 0.2;
// Prefer smaller shifts
var shiftPenalty = Math.abs(k) * 0.02;
var score = 1 - penalty - shiftPenalty;
if(score > bestScore){
bestScore = score;
bestShift = k;
}
}
return bestShift;
}
/* ============================================
キー表示フォーマット(オク下/オク上対応)
============================================ */
function formatKeyShift(shift){
var displayShift = shift;
var label = '';
if(shift < -6){ displayShift = shift + 12; label = 'オク下'; }
else if(shift > 6){ displayShift = shift - 12; label = 'オク上'; }
var text = displayShift === 0 ? '±0' : (displayShift > 0 ? '+'+displayShift : ''+displayShift);
return { text: text, displayShift: displayShift, label: label, badgeClass: displayShift < 0 ? 'down' : (displayShift === 0 ? 'zero' : 'up') };
}
/* ============================================
結果表示
============================================ */
function showResults(){
var el = document.getElementById('vrc-results');
el.classList.add('show');
var grid = document.getElementById('vrc-result-grid');
grid.innerHTML =
'
最低音
' + midiToName(state.lowest) + '
' +
'地声最高音
' + midiToName(state.chest) + '
' +
'裏声最高音
' + midiToName(state.falsetto) + '
';
var rangeWidth = (state.falsetto || state.chest) - state.lowest;
var summary = document.getElementById('vrc-result-summary');
var desc = '';
var rangePctR = Math.round(rangeWidth / 48 * 100);
if(rangePctR >= 90){
desc = 'あなたの音域は平均よりもかなり広めです。幅広いジャンルの曲に対応できます。';
} else if(rangePctR >= 70){
desc = 'あなたの音域は平均よりも広めです。多くのポップスが歌いやすい音域を持っています。';
} else if(rangePctR >= 50){
desc = 'あなたの音域は平均よりもやや広めです。キー調整を活用すればさらに多くの曲が歌えます。';
} else if(rangePctR >= 30){
desc = 'あなたの音域は標準的です。キー調整を上手に使えばほとんどの曲に対応できます。';
} else {
desc = 'あなたの音域はやや狭めです。キーを調整することで多くの曲が歌いやすくなります。';
}
desc += '音域の幅は' + rangeWidth + '半音(約' + Math.round(rangeWidth/12*10)/10 + 'オクターブ)です。';
summary.innerHTML = desc;
}
function showSongs(){
var el = document.getElementById('vrc-songs');
el.classList.add('show');
renderSongTable();
}
function getActiveAdjustments(){
return state.songMode === 'vocaloid' ? state.adjustmentsVocal : state.adjustments;
}
function renderSongTable(){
var body = document.getElementById('vrc-songs-body');
var filtered = getActiveAdjustments().filter(function(s){
// Filter
if(state.filter === 'male' && s.suit !== '男性') return false;
if(state.filter === 'female' && s.suit !== '女性') return false;
// Search
if(state.searchQuery){
var q = state.searchQuery.toLowerCase();
if(s.title.toLowerCase().indexOf(q)===-1 && s.artist.toLowerCase().indexOf(q)===-1) return false;
}
return true;
});
// Sort: 歌いやすい曲 = narrowest vocal range, others = popularity
if(state.filter === 'easy'){
filtered.sort(function(a,b){
var aRange = (a.falsetto || a.chest) - a.low;
var bRange = (b.falsetto || b.chest) - b.low;
return aRange - bRange || a.rank - b.rank;
});
} else {
filtered.sort(function(a,b){ return a.rank - b.rank; });
}
var show = state.searchQuery ? filtered.length : Math.min(state.showCount, filtered.length);
var html = '';
for(var i = 0; i < show; i++){
var s = filtered[i];
var fmt = formatKeyShift(s.shift);
var songRange = midiToName(s.low) + '〜' + midiToName(s.chest);
if(s.falsetto) songRange += '(裏声' + midiToName(s.falsetto) + ')';
var octLabel = fmt.label ? '' + fmt.label + '' : '';
html += '| ' +
' ' + s.title + ' ' +
'' + s.artist + ' ' +
' | ' +
' ' + fmt.text + ' ' + octLabel +
' | ' +
' ' + songRange + ' ' +
' |
';
}
body.innerHTML = html;
var moreBtn = document.getElementById('vrc-show-more');
moreBtn.style.display = (show < filtered.length && !state.searchQuery) ? 'block' : 'none';
}
/* ============================================
アルバムアートワーク(Apple Music)
============================================ */
var SONG_COVERS = {
"ライラック":"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/4c/3b/b2/4c3bb247-3be8-0c57-aa9a-7f1775a7b7a8/24UMGIM32931.rgb.jpg/300x300bb.jpg",
"怪獣の花唄":"https://is1-ssl.mzstatic.com/image/thumb/Music116/v4/8a/eb/75/8aeb7526-6fe4-a81a-5dce-c5d026e7f036/4547366655728.jpg/300x300bb.jpg",
"残酷な天使のテーゼ":"https://is1-ssl.mzstatic.com/image/thumb/Music/c7/68/d7/mzi.anmgzqdl.jpg/300x300bb.jpg",
"サウダージ":"https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/78/f2/bb/78f2bb53-bef0-9ab2-6cc2-7a89af8eb2ac/jacket_SRCL04901B00Z_550.jpg/300x300bb.jpg",
"マリーゴールド":"https://is1-ssl.mzstatic.com/image/thumb/Music125/v4/67/34/90/67349087-0609-2be4-4963-fd4682ae8c46/190295600341.jpg/300x300bb.jpg",
"さよならエレジー":"https://is1-ssl.mzstatic.com/image/thumb/Music114/v4/61/89/9e/61899ec0-432b-d07f-0f6d-04fb01572ecd/jacket_ESXX01814B00Z_550.jpg/300x300bb.jpg",
"ドライフラワー":"https://is1-ssl.mzstatic.com/image/thumb/Music125/v4/d2/23/07/d2230757-3f9c-fccd-3cc8-333f7be09716/4547366478655.jpg/300x300bb.jpg",
"かわいいだけじゃだめですか?":"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/71/a6/91/71a691bc-b5a6-7903-4b13-608611c2f2b3/4580789670411.jpg/300x300bb.jpg",
"ダーリン":"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/7b/b9/27/7bb92776-1f4a-06c6-e36e-de889f2b8620/25UMGIM01757.rgb.jpg/300x300bb.jpg",
"小さな恋のうた":"https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/62/3b/85/623b858b-d88f-2c04-cb5d-4923e1d64190/mzi.beiflkay.tif/300x300bb.jpg",
"水平線":"https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/bd/d6/8f/bdd68f1b-8588-3e91-faab-ab9d7ca35c2f/21UMGIM28078.rgb.jpg/300x300bb.jpg",
"ケセラセラ":"https://is1-ssl.mzstatic.com/image/thumb/Music116/v4/25/22/db/2522db04-1404-086a-c8a2-253484030b36/23UMGIM37150.rgb.jpg/300x300bb.jpg",
"高嶺の花子さん":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/ad/35/1f/ad351f01-3b1e-e723-77db-3a7d88221e5e/00602577303753.rgb.jpg/300x300bb.jpg",
"奏(かなで)":"https://is1-ssl.mzstatic.com/image/thumb/Music114/v4/5f/e6/5a/5fe65a3b-98a0-b453-02a2-aad961438a39/00602557541564.rgb.jpg/300x300bb.jpg",
"点描の唄":"https://is1-ssl.mzstatic.com/image/thumb/Music116/v4/34/27/08/3427087f-2d39-4847-9b19-052dd3589014/18UMGIM36633.rgb.jpg/300x300bb.jpg",
"チェリー":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/29/c0/30/29c030c7-ae9c-a201-ef47-bf9a8b2ed31a/00600406212191.rgb.jpg/300x300bb.jpg",
"366日":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/bd/c1/f7/bdc1f77e-f4a1-3e8f-d9fe-9910be6a4196/mzi.gzufbkti.jpg/300x300bb.jpg",
"シングルベッド":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/aa/d3/a4/aad3a4c4-b574-ed2d-416c-821773e3ed07/jacket_MHCL30151B00Z_550.jpg/300x300bb.jpg",
"晩餐歌":"https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/67/03/c9/6703c973-c9da-1bd8-cc81-fe315b951231/4582649065306.jpg/300x300bb.jpg",
"青と夏":"https://is1-ssl.mzstatic.com/image/thumb/Music116/v4/34/27/08/3427087f-2d39-4847-9b19-052dd3589014/18UMGIM36633.rgb.jpg/300x300bb.jpg",
"ひまわりの約束":"https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/84/c7/0b/84c70b45-4db3-72db-4f3c-cbffe6b2cefa/00602557538274.rgb.jpg/300x300bb.jpg",
"丸の内サディスティック":"https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/bd/da/b4/bddab4d2-176e-6438-76dc-54bd4eac3914/18UMGIM22257.rgb.jpg/300x300bb.jpg",
"チャンカパーナ":"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/92/46/0f/92460f79-c5d2-ee56-34bd-ded18519fb67/4517331087649.jpg/300x300bb.jpg",
"最後の雨":"https://is1-ssl.mzstatic.com/image/thumb/Music/v4/39/ae/e4/39aee461-6a35-41d8-b4ba-be4da7d13f67/COKM_31561.jpg/300x300bb.jpg",
"Story":"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/a6/e0/7e/a6e07e13-7bb3-2cc7-0f36-45ca28138d8e/06UMGIM42910.rgb.jpg/300x300bb.jpg",
"Soranji":"https://is1-ssl.mzstatic.com/image/thumb/Music122/v4/e1/ec/d4/e1ecd4b4-4f2f-c49e-fa95-958c6b4cf7ab/22UM1IM16537.rgb.jpg/300x300bb.jpg",
"ハナミズキ":"https://is1-ssl.mzstatic.com/image/thumb/Music128/v4/b4/ae/05/b4ae053f-54ad-a984-f0bb-84f35c7c1c73/00600406609519.rgb.jpg/300x300bb.jpg",
"恋人ごっこ":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/87/88/05/8788059e-117b-439e-f3f1-e0e2da15db31/bigup13058482.jpg/300x300bb.jpg",
"大阪LOVER":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/1b/06/4b/1b064b9d-4912-9a2c-94fc-4caadf42fbab/00044002558121.rgb.jpg/300x300bb.jpg",
"糸":"https://is1-ssl.mzstatic.com/image/thumb/Music122/v4/ad/d7/78/add7785f-da7a-26c9-2157-1c6057bca0bb/YCCI10481P000000.jpg/300x300bb.jpg",
"世界が終るまでは…":"https://is1-ssl.mzstatic.com/image/thumb/Music112/v4/cc/2f/5c/cc2f5c71-d976-d503-f107-ef0f71a14b84/wands-0121.jpg/300x300bb.jpg",
"愛をこめて花束を":"https://is1-ssl.mzstatic.com/image/thumb/Music/a8/b4/a0/mzi.snoeojdw.jpg/300x300bb.jpg",
"キセキ":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/59/71/a5/5971a58d-9b81-1c64-195e-9e896c804d06/00044002897312.rgb.jpg/300x300bb.jpg",
"Lemon":"https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/86/b0/9d/86b09d63-367a-563a-1e24-d5d62c220ac8/jacket_SRCL09749B00Z_550.jpg/300x300bb.jpg",
"Bling-Bang-Bang-Born":"https://is1-ssl.mzstatic.com/image/thumb/Music116/v4/12/81/dc/1281dc28-48e6-5ff7-642d-6cf4d487d9e0/4547366660890.jpg/300x300bb.jpg",
"カブトムシ":"https://is1-ssl.mzstatic.com/image/thumb/Music123/v4/bb/b1/26/bbb12681-ba77-f107-2a16-c684f685b464/dj.jqehhfti.jpg/300x300bb.jpg",
"幾億光年":"https://is1-ssl.mzstatic.com/image/thumb/Music116/v4/7e/e5/e6/7ee5e6f7-9c93-f121-fca0-e371dbce8f6e/4547366665246.jpg/300x300bb.jpg",
"アイノカタチ":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/b8/0d/73/b80d73a6-3519-9990-837b-d3f009fa55df/jacket_BVXX01113B00Z_550.jpg/300x300bb.jpg",
"イケナイ太陽":"https://is1-ssl.mzstatic.com/image/thumb/Music114/v4/a9/9d/09/a99d0919-788a-4f0f-0c7b-045adeccbb45/jacket_SRCL06825B00Z_550.jpg/300x300bb.jpg",
"クリスマスソング":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/29/ec/e8/29ece87b-ca51-50eb-30a5-6b3efdeb8767/00602577303920.rgb.jpg/300x300bb.jpg",
"島人ぬ宝":"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/b8/aa/55/b8aa55b9-c925-bdbc-50c4-cb98c5313657/cover.jpg/300x300bb.jpg",
"怪獣":"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/44/35/c7/4435c703-3647-654b-16a7-ab74885f71da/VEATP-43484.jpg/300x300bb.jpg",
"Bunny Girl":"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/f6/6e/c2/f66ec203-aee5-74dc-c8e3-1bd9b2134d49/24UM1IM31705.rgb.jpg/300x300bb.jpg",
"歌うたいのバラッド":"https://is1-ssl.mzstatic.com/image/thumb/Music118/v4/d2/0e/85/d20e854b-865c-6c54-f4ac-a0500dbcef6a/VEATP-34575.jpg/300x300bb.jpg",
"気まぐれロマンティック":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/49/82/a0/4982a066-6dbf-5482-d68b-aa154848ee6c/jacket_ESCL03139B00Z_550.jpg/300x300bb.jpg",
"僕のこと":"https://is1-ssl.mzstatic.com/image/thumb/Music122/v4/37/30/a9/3730a9f0-50d3-e4e1-9f4d-620b30380414/18UMGIM80785.rgb.jpg/300x300bb.jpg",
"シャルル":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/06/fa/ea/06faea93-525a-ed28-5521-a3774a7afc35/4589686423939_cover.jpg/300x300bb.jpg",
"シンデレラボーイ":"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/d9/5c/85/d95c85ae-0227-9b9b-0d6d-ae1e45bb1ee5/25UMGIM98719.rgb.jpg/300x300bb.jpg",
"花束のかわりにメロディーを":"https://is1-ssl.mzstatic.com/image/thumb/Music125/v4/d1/3e/e3/d13ee389-2cbc-62d5-de57-7945db378685/jacket_SRCL08919B00Z_550.jpg/300x300bb.jpg",
"Pretender":"https://is1-ssl.mzstatic.com/image/thumb/Music113/v4/9c/7d/88/9c7d883d-4477-bf68-f56f-a726030a63a2/PCCA_04822.jpg/300x300bb.jpg",
"コイスルオトメ":"https://is1-ssl.mzstatic.com/image/thumb/Music114/v4/26/e1/47/26e147d9-3f35-203f-ef83-9d8cf0a62b8b/jacket_ESCL03528B00Z_550.jpg/300x300bb.jpg",
"メロディー":"https://is1-ssl.mzstatic.com/image/thumb/Music114/v4/9a/1f/95/9a1f95f7-260c-7399-6c07-920cf889d452/jacket_MHXX00952B00Z_550.jpg/300x300bb.jpg",
"I LOVE YOU":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/6c/6a/4d/6c6a4d8b-c0c9-7d3b-c9c0-1a9c0c7c5f4b/jacket_SRXX01408B00Z_550.jpg/300x300bb.jpg",
"白い恋人達":"https://is1-ssl.mzstatic.com/image/thumb/Music49/v4/61/c2/53/61c253e3-f57d-006a-a8a6-a2a2710ec0ed/VEATP-33298.jpg/300x300bb.jpg",
"3月9日":"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/79/8b/74/798b74e3-d88b-6107-48cb-1c03a7f86dba/VEATP-44545.jpg/300x300bb.jpg",
"チェックのワンピース":"https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/26/b6/e7/26b6e7e2-1cbb-4dde-edfb-e44e1f881cbd/00602577303739.rgb.jpg/300x300bb.jpg",
"アゲハ蝶":"https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/74/3f/9c/743f9c6d-873f-07b5-6dd9-b1e3e87cee7e/jacket_SEXX00739B00Z_550.jpg/300x300bb.jpg",
"IRIS OUT":"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/e0/f9/f1/e0f9f1f3-a088-b28d-6760-a28accabc705/4547366775181.jpg/300x300bb.jpg",
"ハレンチ":"https://is1-ssl.mzstatic.com/image/thumb/Music125/v4/bd/c0/80/bdc08023-823d-b74c-0b58-e161fe8ae68b/190296458705.jpg/300x300bb.jpg",
"はいよろこんで":"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/0e/80/f2/0e80f216-59c5-614e-ba6b-d16645370b06/4571640152092_cover.jpg/300x300bb.jpg",
"虹":"https://is1-ssl.mzstatic.com/image/thumb/Music125/v4/2e/b0/3e/2eb03e07-08bf-df9e-2815-1b88f8146e79/4547366482928.jpg/300x300bb.jpg",
"津軽海峡・冬景色":"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/fc/f9/5a/fcf95aee-2619-52fc-66a1-89682dec2008/cover.jpg/300x300bb.jpg",
"インフェルノ":"https://is1-ssl.mzstatic.com/image/thumb/Music122/v4/d6/9e/54/d69e54ca-2d82-f291-d745-9a706aa26940/19UMGIM59475.rgb.jpg/300x300bb.jpg",
"倍倍FIGHT!":"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/dc/e5/64/dce56488-6cca-c862-c407-fc93ff38e7b9/4580789631009.jpg/300x300bb.jpg",
"タッチ":"https://is1-ssl.mzstatic.com/image/thumb/Music3/v4/89/3b/3c/893b3cf3-ecbe-f2cb-adc9-323f1d3898c6/PCSP_01801_itunes.png/300x300bb.jpg",
"栄光の架橋":"https://is1-ssl.mzstatic.com/image/thumb/Music/24/05/c4/mzi.wyjzyljh.jpg/300x300bb.jpg",
"クスシキ":"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/a0/cc/26/a0cc2636-c5ce-3058-fe52-7b65b90e8ec1/25UMGIM77456.rgb.jpg/300x300bb.jpg",
"アイドル":"https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/3a/17/eb/3a17eb30-eacb-82eb-51df-d1321dbb55bc/197188492205.jpg/300x300bb.jpg",
"First Love":"https://is1-ssl.mzstatic.com/image/thumb/Music128/v4/d1/a6/81/d1a681b9-aa68-6148-5a50-69fd4c95cc37/00602567241966.rgb.jpg/300x300bb.jpg",
"TSUNAMI":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/79/e7/70/79e7709a-96a4-9029-e22c-f9252575a6ec/VEATP-30872.jpg/300x300bb.jpg",
"粉雪":"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/36/1b/f2/361bf2e3-ae78-e0d9-5092-d4c428e5a9fa/VEATP-44550.jpg/300x300bb.jpg",
"CITRUS":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/06/a1/d8/06a1d861-418e-3fb1-e873-356f7f49090b/ANTCD-A0000002767.jpg/300x300bb.jpg",
"HANABI":"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/05/dd/70/05dd70e7-7e34-1836-4683-ff416d11a35c/dj.xwkpljqa.jpg/300x300bb.jpg",
"雪の華":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/7d/1d/81/7d1d818e-d062-6c58-b977-8c389d1388c3/jacket_AICL01494B00Z_550.jpg/300x300bb.jpg",
"わたしの一番かわいいところ":"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/63/24/b6/6324b6d9-8535-8783-3a2a-bc838dd45826/4582467702803.jpg/300x300bb.jpg",
"DAN DAN 心魅かれてく":"https://is1-ssl.mzstatic.com/image/thumb/Music116/v4/62/b3/40/62b340c8-6c0c-168b-3ccc-ed80a0297312/859759846113_cover.png/300x300bb.jpg",
"残響散歌":"https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/c9/40/9e/c9409e84-c0ea-c389-3be3-2343ec3dfb3b/4547366539806.jpg/300x300bb.jpg",
"猫":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/f8/e0/c3/f8e0c344-3035-ce6c-aaf4-9efd2cfb8a4b/jacket_SRXX02423B00Z_550.jpg/300x300bb.jpg",
"花束":"https://is1-ssl.mzstatic.com/image/thumb/Music114/v4/40/e5/93/40e59322-1405-fbd9-81e0-c37f887d7133/00602577303814.rgb.jpg/300x300bb.jpg",
"ただ君に晴れ":"https://is1-ssl.mzstatic.com/image/thumb/Music122/v4/e3/e5/7a/e3e57a00-ed2d-69d6-bf00-347d7c37f3bd/PA00074572_0_90261_jacket.jpg/300x300bb.jpg",
"裸の心":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/85/0b/1b/850b1b4c-f529-6828-4b2b-a5a187ec61c6/190295235017.jpg/300x300bb.jpg",
"炎":"https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/88/ce/67/88ce6774-3358-e238-5244-1b0788adea50/4547366475562.jpg/300x300bb.jpg",
"ヒロイン":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/5d/d1/f0/5dd1f0aa-4b50-e6b1-73b1-fb740989afba/00602577303906.rgb.jpg/300x300bb.jpg",
"APT.":"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/2d/1a/7d/2d1a7d91-587e-0ceb-d434-327bd66d9e86/075679628312.jpg/300x300bb.jpg",
"ダンスホール":"https://is1-ssl.mzstatic.com/image/thumb/Music122/v4/b7/20/05/b7200500-e97d-8c79-7cf2-99a6fb1aaf4d/22UMGIM50592.rgb.jpg/300x300bb.jpg",
"I wonder":"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/b4/32/b8/b432b806-4718-07d6-e37f-59ee11893da4/ANTCD-A0000013226.jpg/300x300bb.jpg",
"愛唄":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/d5/d2/6d/d5d26d86-26db-367e-5f6a-ace2e0a9e9ff/00028945075099.rgb.jpg/300x300bb.jpg",
"未来予想図 Ⅱ":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/7b/7c/36/7b7c36bb-eb3e-52bc-bbc4-0301e448ba97/jacket_ESCB01018B00Z_550.jpg/300x300bb.jpg",
"魔法の絨毯":"https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/af/1d/c2/af1dc23c-09fb-3f77-4288-27d15e60bb00/190296596384.jpg/300x300bb.jpg",
"天体観測":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/50/85/26/50852674-2973-742b-d4ea-8d0bae94f344/TFCC-87080WW.jpg/300x300bb.jpg",
"時の流れに身をまかせ":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/61/fa/f1/61faf1ac-fb6c-e8c8-b053-14ff50b704e2/00044002896971.rgb.jpg/300x300bb.jpg",
"眠り姫":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/7e/b4/76/7eb47679-4c56-9275-1a39-77ad820edb7f/TFCC-86389.jpg/300x300bb.jpg",
"紅蓮華":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/b3/ae/3b/b3ae3bd0-47cc-6349-1d0d-a01928796a62/jacket_VVXX00432B00Z_550.jpg/300x300bb.jpg",
"Love so sweet":"https://is1-ssl.mzstatic.com/image/thumb/Music113/v4/8e/10/2a/8e102abd-d981-e370-82db-2ae5c98baac3/194491537920.jpg/300x300bb.jpg",
"ビリミリオン":"https://is1-ssl.mzstatic.com/image/thumb/Music112/v4/2b/b8/c9/2bb8c92c-9d9a-9463-958c-ec0d6ce60fb5/4547366600032.jpg/300x300bb.jpg",
"115万キロのフィルム":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/40/e8/2d/40e82dfb-55b4-7016-1127-357ab78d3f7f/jk.jpg/300x300bb.jpg",
"ロビンソン":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/9a/26/3b/9a263bc6-cebd-04df-c362-2a0868d277dd/00602557728743.rgb.jpg/300x300bb.jpg",
"真夏の果実":"https://is1-ssl.mzstatic.com/image/thumb/Music5/v4/1d/85/cd/1d85cd11-48db-8ca2-8c58-ff0900a1bac1/VEATP-31058.jpg/300x300bb.jpg"
};
/* ============================================
あなたにぴったりの曲
============================================ */
function showPicks(){
var el = document.getElementById('vrc-picks');
var track = document.getElementById('vrc-picks-track');
el.classList.add('show');
// Sort by closest to original key (smallest absShift), then by rank
state.picksSorted = getActiveAdjustments().slice().sort(function(a,b){
return a.absShift - b.absShift || a.rank - b.rank;
});
state.picksShown = 5;
renderPicks();
}
function renderPicks(){
var track = document.getElementById('vrc-picks-track');
var items = state.picksSorted.slice(0, state.picksShown);
var html = '';
items.forEach(function(s){
var fmt = formatKeyShift(s.shift);
var badgeText = fmt.text;
if(fmt.label) badgeText += ' ' + fmt.label;
var badgeClass = fmt.badgeClass;
var coverImg = SONG_COVERS[s.title] || '';
var coverHtml = coverImg
? '
'
: '' + s.title.charAt(0) + '
';
var appleUrl = 'https://music.apple.com/jp/search?term=' + encodeURIComponent(s.title + ' ' + s.artist);
html += '' +
'
' + coverHtml +
'
' + badgeText + '
' +
'
' +
'
' +
'
' + s.title + '
' +
'
' + s.artist + '
' +
'
';
});
if(state.picksShown < state.picksSorted.length){
html += 'もっと見る →
';
}
track.innerHTML = html;
var moreBtn = document.getElementById('vrc-picks-more-btn');
if(moreBtn){
moreBtn.addEventListener('click', function(){
state.picksShown += 5;
renderPicks();
});
}
}
/* ============================================
シードデータ & パーセンタイル
============================================ */
function seededRng(seed){
return function(){
seed = (seed * 16807) % 2147483647;
return (seed - 1) / 2147483646;
};
}
function generateSeedData(){
var rng = seededRng(12345);
var data = [];
var norm = function(){
var u1 = rng(), u2 = rng();
return Math.sqrt(-2*Math.log(u1))*Math.cos(2*Math.PI*u2);
};
var clamp = function(v,lo,hi){ return Math.max(lo, Math.min(hi, Math.round(v))); };
// Male: 300 entries
for(var i=0;i<300;i++){
data.push({
g:'male',
a: clamp(30+norm()*8, 15, 60),
l: clamp(42+norm()*3, 36, 55),
c: clamp(68+norm()*3, 58, 76),
f: clamp(73+norm()*3, 63, 82)
});
}
// Female: 300 entries
for(var j=0;j<300;j++){
data.push({
g:'female',
a: clamp(30+norm()*8, 15, 60),
l: clamp(55+norm()*3, 46, 65),
c: clamp(73+norm()*3, 65, 80),
f: clamp(77+norm()*3, 70, 84)
});
}
return data;
}
var SEED_DATA = generateSeedData();
// Load user-submitted data from WP (or localStorage fallback)
var USER_DATA = [];
try {
var stored = localStorage.getItem('vrc_reports');
if(stored) USER_DATA = JSON.parse(stored);
} catch(e){}
function getAllData(){
return SEED_DATA.concat(USER_DATA);
}
function getPercentile(values, userValue, higherIsBetter){
var count = 0;
for(var i=0;i userValue) count++;
}
return Math.round((count / values.length) * 100);
}
function calcStats(age, gender){
var allData = getAllData();
var decade = Math.floor(age / 10) * 10;
var decadeLabel = decade + '代';
// Filter by age decade and gender
var group = allData.filter(function(d){
var dDecade = Math.floor(d.a / 10) * 10;
return d.g === gender && dDecade === decade;
});
// If too few matches, use all of same gender
if(group.length < 20){
group = allData.filter(function(d){ return d.g === gender; });
decadeLabel = '';
}
var userRange = (state.falsetto || state.chest) - state.lowest;
var ranges = group.map(function(d){ return (d.f || d.c) - d.l; });
var lows = group.map(function(d){ return d.l; });
var chests = group.map(function(d){ return d.c; });
var falsettos = group.map(function(d){ return d.f; });
return {
decadeLabel: decadeLabel,
genderLabel: gender === 'male' ? '男性' : '女性',
rangePct: getPercentile(ranges, userRange, true),
lowPct: getPercentile(lows, state.lowest, false),
chestPct: getPercentile(chests, state.chest, true),
falsettoPct: getPercentile(falsettos, state.falsetto, true)
};
}
/* ============================================
モーダル / 報告フロー
============================================ */
function showModal(){
var overlay = document.getElementById('vrc-modal-overlay');
overlay.classList.add('show');
// Reset steps
document.getElementById('vrc-step1').style.display = 'block';
document.getElementById('vrc-step2').style.display = 'none';
document.getElementById('vrc-step3').style.display = 'none';
// Show range summary
var rangeDiv = document.getElementById('vrc-modal-range');
var rangeWidth = (state.falsetto || state.chest) - state.lowest;
rangeDiv.innerHTML =
'あなたの音域' +
'' + midiToName(state.lowest) + ' 〜 ' + midiToName(state.chest) +
(state.falsetto ? '(裏声 ' + midiToName(state.falsetto) + ')' : '') + '' +
'音域の幅:' + rangeWidth + '半音(約' + (Math.round(rangeWidth/12*10)/10) + 'オクターブ)';
}
function hideModal(){
document.getElementById('vrc-modal-overlay').classList.remove('show');
}
function showStep2(){
document.getElementById('vrc-step1').style.display = 'none';
document.getElementById('vrc-step2').style.display = 'block';
state.gender = null;
document.getElementById('vrc-g-male').classList.remove('selected');
document.getElementById('vrc-g-female').classList.remove('selected');
document.getElementById('vrc-age').value = '';
document.getElementById('vrc-submit-btn').disabled = true;
}
function selectGender(g){
state.gender = g;
document.getElementById('vrc-g-male').classList.toggle('selected', g==='male');
document.getElementById('vrc-g-female').classList.toggle('selected', g==='female');
checkSubmitReady();
}
function checkSubmitReady(){
var age = parseInt(document.getElementById('vrc-age').value);
document.getElementById('vrc-submit-btn').disabled = !(state.gender && age >= 10 && age <= 80);
}
function submitReport(){
var age = parseInt(document.getElementById('vrc-age').value);
if(!state.gender || !age) return;
// Save to localStorage (fallback for WP REST API)
var report = {
g: state.gender,
a: age,
l: state.lowest,
c: state.chest,
f: state.falsetto,
ts: Date.now()
};
USER_DATA.push(report);
try { localStorage.setItem('vrc_reports', JSON.stringify(USER_DATA)); } catch(e){}
// Try WP REST API
if(typeof wp !== 'undefined' && wp.apiFetch){
try {
wp.apiFetch({
path: '/vrc/v1/report',
method: 'POST',
data: report
}).catch(function(){});
} catch(e){}
}
// Show percentile results
var stats = calcStats(age, state.gender);
showStep3(stats);
}
function getRangeEvaluation(rangePct){
if(rangePct >= 90) return 'とても広い';
if(rangePct >= 70) return '広い';
if(rangePct >= 50) return 'やや広い';
if(rangePct >= 30) return '標準的';
if(rangePct >= 15) return 'やや狭い';
return '狭め';
}
function showStep3(stats){
document.getElementById('vrc-step2').style.display = 'none';
document.getElementById('vrc-step3').style.display = 'block';
var prefix = stats.decadeLabel ? stats.decadeLabel + 'の' : '';
var rangeWidth = (state.falsetto || state.chest) - state.lowest;
var rangeEval = getRangeEvaluation(stats.rangePct);
var content = document.getElementById('vrc-pct-content');
content.innerHTML =
'' +
'' + midiToName(state.lowest) + ' 〜 ' + midiToName(state.chest) +
(state.falsetto ? '(裏声 ' + midiToName(state.falsetto) + ')' : '') + '' +
'' + rangeWidth + '半音(約' + (Math.round(rangeWidth/12*10)/10) + 'オクターブ)' +
'' + rangeEval + '' +
'
' +
'' +
'' + prefix + stats.genderLabel + 'の' +
'' + stats.rangePct + '%' +
'より音域が広いです' +
'
' +
'' +
'
最低音' + stats.lowPct + '%より低い
' +
'
地声最高音' + stats.chestPct + '%より高い
' +
'
裏声最高音' + stats.falsettoPct + '%より高い
' +
'
';
}
/* ============================================
イベントハンドラ
============================================ */
function init(){
buildPiano();
// Mode buttons
['lowest','chest','falsetto'].forEach(function(m){
document.getElementById('vrc-btn-'+m).addEventListener('click', function(e){
e.preventDefault();
if(state.currentNote !== null){
setRangeValue(m, state.currentNote);
}
});
});
// Diagnose button
document.getElementById('vrc-diagnose-btn').addEventListener('click', function(){
diagnose();
});
// Reset button
document.getElementById('vrc-reset-btn').addEventListener('click', function(){
resetAll();
});
// Report button
document.getElementById('vrc-report-btn').addEventListener('click', function(){
showModal();
});
// Modal CTA
document.getElementById('vrc-cta-btn').addEventListener('click', function(){
showStep2();
});
// Gender buttons
document.getElementById('vrc-g-male').addEventListener('click', function(){ selectGender('male'); });
document.getElementById('vrc-g-female').addEventListener('click', function(){ selectGender('female'); });
// Age input
document.getElementById('vrc-age').addEventListener('input', function(){ checkSubmitReady(); });
// Submit button
document.getElementById('vrc-submit-btn').addEventListener('click', function(){ submitReport(); });
// Modal close
document.getElementById('vrc-modal-x').addEventListener('click', function(){ hideModal(); });
document.getElementById('vrc-modal-done').addEventListener('click', function(){ hideModal(); });
document.getElementById('vrc-modal-overlay').addEventListener('click', function(e){
if(e.target === this) hideModal();
});
// Search
document.getElementById('vrc-search').addEventListener('input', function(){
state.searchQuery = this.value;
renderSongTable();
});
// Filters
var filterBtns = document.querySelectorAll('.vrc-filter-btn');
filterBtns.forEach(function(btn){
btn.addEventListener('click', function(){
filterBtns.forEach(function(b){ b.classList.remove('active'); });
this.classList.add('active');
state.filter = this.dataset.filter;
state.showCount = 5;
renderSongTable();
});
});
// Show more
document.getElementById('vrc-show-more').addEventListener('click', function(){
state.showCount += 5;
renderSongTable();
});
// Song tabs (normal / vocaloid)
var tabBtns = document.querySelectorAll('.vrc-tab-btn');
tabBtns.forEach(function(btn){
btn.addEventListener('click', function(){
tabBtns.forEach(function(b){ b.classList.remove('active'); });
this.classList.add('active');
state.songMode = this.dataset.tab;
state.showCount = 5;
state.filter = 'all';
state.searchQuery = '';
document.getElementById('vrc-search').value = '';
// Update filter buttons
filterBtns.forEach(function(b){ b.classList.remove('active'); });
document.querySelector('.vrc-filter-btn[data-filter="all"]').classList.add('active');
// Hide/show suit filters for vocaloid
var suitBtns = document.querySelectorAll('.vrc-filter-btn[data-filter="male"],.vrc-filter-btn[data-filter="female"]');
suitBtns.forEach(function(b){ b.style.display = state.songMode === 'vocaloid' ? 'none' : ''; });
// Update ranking link
var refLink = document.getElementById('vrc-ranking-ref');
if(refLink){
if(state.songMode === 'vocaloid'){
refLink.href = 'https://www.clubdam.com/genre/vocaloid/ranking_secondhalf.html';
refLink.textContent = 'DAM ボカロランキング';
} else {
refLink.href = 'https://www.clubdam.com/ranking/annual2025/';
refLink.textContent = 'DAM 2025年間ランキング';
}
}
// Re-render if diagnosed
if(state.diagnosed){
showPicks();
renderSongTable();
}
});
});
// X share buttons
function shareOnX(){
var low = state.lowest ? midiToName(state.lowest) : '';
var chest = state.chest ? midiToName(state.chest) : '';
var fal = state.falsetto ? midiToName(state.falsetto) : '';
var rangeWidth = (state.falsetto || state.chest) - state.lowest;
var rangePct = Math.round(rangeWidth / 48 * 100);
var rangeEval = getRangeEvaluation(rangePct);
var rangeText = low + ' 〜 ' + chest + (fal ? '(裏声 ' + fal + ')' : '');
var text = '私の音域は ' + rangeText + ' で「' + rangeEval + '」でした!\n#音域チェッカー';
var url = 'https://label-clach.jp/?p=2717';
var intentUrl = 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(text) + '&url=' + encodeURIComponent(url);
window.open(intentUrl, '_blank', 'width=550,height=420');
}
var shareBtn = document.getElementById('vrc-share-x');
if(shareBtn) shareBtn.addEventListener('click', shareOnX);
var shareBtnModal = document.getElementById('vrc-share-x-modal');
if(shareBtnModal) shareBtnModal.addEventListener('click', shareOnX);
// Drag scroll with momentum for picks track
var picksTrack = document.getElementById('vrc-picks-track');
if(picksTrack){
var ds = {active:false, startX:0, scrollStart:0, moved:false, lastX:0, lastT:0, velocity:0, raf:0};
picksTrack.addEventListener('dragstart', function(e){ e.preventDefault(); });
picksTrack.addEventListener('mousedown', function(e){
cancelAnimationFrame(ds.raf);
ds.active = true;
ds.moved = false;
ds.startX = e.pageX;
ds.lastX = e.pageX;
ds.lastT = Date.now();
ds.velocity = 0;
ds.scrollStart = picksTrack.scrollLeft;
picksTrack.style.cursor = 'grabbing';
picksTrack.style.userSelect = 'none';
});
window.addEventListener('mousemove', function(e){
if(!ds.active) return;
var dx = e.pageX - ds.startX;
if(Math.abs(dx) > 5) ds.moved = true;
e.preventDefault();
var now = Date.now();
var dt = now - ds.lastT;
if(dt > 0) ds.velocity = (ds.lastX - e.pageX) / dt;
ds.lastX = e.pageX;
ds.lastT = now;
picksTrack.scrollLeft = ds.scrollStart - dx;
});
window.addEventListener('mouseup', function(e){
if(!ds.active) return;
ds.active = false;
picksTrack.style.cursor = 'grab';
picksTrack.style.removeProperty('user-select');
if(!ds.moved){
var card = e.target.closest('.vrc-pick-card[data-link]');
if(card) window.open(card.dataset.link, '_blank');
} else {
var v = ds.velocity * 300;
var friction = 0.95;
(function coast(){
if(Math.abs(v) < 0.5) return;
picksTrack.scrollLeft += v * 0.016;
v *= friction;
ds.raf = requestAnimationFrame(coast);
})();
}
});
picksTrack.addEventListener('touchstart', function(e){
cancelAnimationFrame(ds.raf);
}, {passive:true});
picksTrack.style.cursor = 'grab';
}
}
if(document.readyState === 'loading'){
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();