웹사이트 바로가기

이 웹사이트의 기능은 다음과 같습니다.

  • 스케일 목록을 데이터베이스에서 읽어서 테이블 형태로 나열 (PHP)
  • 각 목록을 클릭하면 스케일의 정보와 악보, 소리듣기 기능을 제공 (abcjs 이용)
  • 스케일의 정보는 이름, 별칭(Alias), 설명(Description), 중요도(Priority), 패턴이 있음
  • 악보 및 사운드 생성은 스케일의 Pattern 정보를 이용하여 생성 (PHP 음악 스케일 구하기)
  • 조옮김(Transpose), 이명동음 표시기능 제공

 


<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Musical Scales</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<link href="./res/abcjs-audio.css" media="all" rel="stylesheet" type="text/css" />
<script src="./res/abcjs_basic_5.9.1-min.js" type="text/javascript"></script>
<style>
</style>
</head>
<body class="container">
<?php
include "init-db.php";
// if($mysqli){echo "MySQL 접속 성공";}
// else{echo "MySQL 접속 실패";}
$sql = 'SELECT * FROM music_scale order by priority desc, name';
$res = $mysqli -> query($sql);
?>
<header>
<h2>Musical Scales</h2>
</header>
<nav>
</nav>
<section>
<table id="freq-table" class="table table-hover">
<thead>
<tr class="table-info">
<th>Name</th>
<th>Alias</th>
<th>Priority</th>
</tr>
</thead>
<tbody id="freq-tbody">
<?php while($row = mysqli_fetch_array($res)) { ?>
<tr class="each-scale" data-description="<?=$row['description']?>" data-pattern="<?=$row['pattern']?>" data-priority="<?=$row['priority']?>">
<td class="scale-title">
<?php
$flt0 = str_replace("Double-Flat ", "𝄫", $row['name']);
$flt1 = str_replace("Flat ", "♭" , $flt0);
$flt2 = str_replace("Sharp ", "♯", $flt1);
echo str_replace("Nat. ", "♮", $flt2);
?>
</td>
<td class="scale-alias">
<?=$row['alias']?>
</td>
<td class="area-priority">
<span class="badge badge-warning">
<?php
for($i = 1; $i <= $row['priority']; $i++ ){
echo "★";
}
?>
</span>
</td>
</tr>
<?php }?>
</tbody>
</table>
</section>
<article>
<div class="modal fade">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<table>
<tbody>
<tr>
<th>Name</th>
<td id="mod-cont-name"></td>
</tr>
<tr>
<th>Alias</th>
<td id="mod-cont-alias"></td>
</tr>
<tr>
<th>Pattern</th>
<td id="mod-cont-pattern"></td>
</tr>
<tr>
<th>Priority</th>
<td><span id="mod-cont-prior" class="badge badge-warning"></span></td>
</tr>
<tr>
<th colspan="2">Description</th>
</tr>
<tr>
<td id="mod-cont-desc" colspan="2"></td>
</tr>
</tbody>
</table>
<hr>
<div>
<label>Transpose</label>
<select id=transpose>
<option value="0">C</option>
<option value="1">C♯(D♭)</option>
<option value="2">D</option>
<option value="3">E♭(D♯)</option>
<option value="4">E</option>
<option value="5">F</option>
<option value="6">F♯(G♭)</option>
<option value="7">G</option>
<option value="8">A♭(G♯)</option>
<option value="9">A</option>
<option value="10">B♭(A♯)</option>
<option value="11">B</option>
</select>
</div>
<div>
<label>Display Enharmonic Notes</label>
<select id="change-enharmonic">
<option value="common">Standard</option>
<option value="sharp">Sharp(♯) only</option>
<option value="flat">Flat(♭) only</option>
</select>
</div>
<hr>
<div id="notation"></div>
<div id="audio"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</article>
<footer>
Copyright by <a href="http://yoonbumtae.com" target="_blank">BGSMM</a>
</footer>
<script>
const NAME_COMMON = ["", "C", "C+", "D", "E-", "E", "F", "F+", "G", "A-", "A", "B-", "B"];
const NAME_FLAT = ["", "C", "D-", "D", "E-", "E", "F", "G-", "G", "A-", "A", "B-", "B"];
const NAME_SHARP = ["", "C", "C+", "D", "D+", "E", "F", "F+", "G", "G+", "A", "A+", "B"];
let glb_currentName = NAME_COMMON
function getScale(param) {
const pattern = param.pattern
const transpose = param.transpose
const patternArr = pattern.split('')
let sum = 1;
const output = {
scaleByName: [],
scaleBySemitone: []
}
const input_ = (semitone) => {
if (semitone > 12) {
output.scaleByName.push(glb_currentName[semitone – 12])
} else {
output.scaleByName.push(glb_currentName[semitone])
}
output.scaleBySemitone.push(semitone)
}
input_(parseInt(transpose) + sum)
for (let o of patternArr) {
sum += parseInt(o)
const dispNum = parseInt(transpose) + sum
input_(dispNum)
}
//console.log(output)
return output
}
// C2 ^C2 _E2 =E2 ^F2 G2 A2 _B2 c2|
// ^: sharp
// =: natural
// _: flat
function translateToAbcjs(scaleBySemitone) {
//console.log(scaleBySemitone)
const outArr = []
const changedNotes = []
for (let each of scaleBySemitone) {
//onsole.log(each)
const nameIndex = each > 12 ? each – 12 : each
const octave = each > 12 ? 1 : 0
//console.log("octave", octave)
const note = (_ => {
if (octave == 0) {
return glb_currentName[nameIndex].substr(0, 1)
} else if (octave == 1) {
return glb_currentName[nameIndex].substr(0, 1).toLowerCase()
}
})()
const postfix = glb_currentName[nameIndex].substr(glb_currentName[nameIndex].length – 1, 1)
//console.log(note, postfix)
if (note.toUpperCase() != postfix) {
const prefix = postfix == "+" ? "^" : "_"
changedNotes.push(note)
outArr.push(prefix + note + "2")
} else {
if (changedNotes.indexOf(note) != -1) {
outArr.push("=" + note + "2")
} else {
outArr.push(note + "2")
}
}
}
//console.log(outArr)
return outArr.join(" ")
}
function displayScore(scaleObj, title, key) {
const scaleStr = `Scale by Name: ${scaleObj.scaleByName}\nScale by Semitone: ${scaleObj.scaleBySemitone}`
console.log(scaleStr)
const translatedScale = translateToAbcjs(scaleObj.scaleBySemitone)
key = key || "C"
var cooleys = `
X: 1
T:
I: speed 50
M:
L: 1/8
R: ${key.trim()} ${title.trim()}
K: C
${translatedScale}|`;
//alert(cooleys)
var visualObj = ABCJS.renderAbc('notation', cooleys, {
responsive: "resize",
staffwidth: 500,
paddingright: 5,
paddingleft: 5,
})[0];
var synthControl = new ABCJS.synth.SynthController();
synthControl.load("#audio", null, {
displayRestart: true,
displayPlay: true,
displayProgress: true
});
synthControl.setTune(visualObj, false);
}
$(".each-scale").on("click", e => {
const eachScale = $(e.target).closest(".each-scale")
const title = eachScale.find(".scale-title").text()
const pattern = eachScale.data("pattern") + ""
const alias = eachScale.data("alias")
const desc = eachScale.data("description")
const prior = eachScale.data("priority")
const priorStar = (_ => {
let str = ""
for (let i = 1; i <= parseInt(prior); i++) {
str += "★"
}
return str
})();
console.log(pattern)
const scaleObj = getScale({
pattern,
transpose: $("#transpose").val()
})
displayScore(scaleObj, title, $("#transpose option:selected").text())
$(".modal-title").text(title)
$("#mod-cont-name").text(title)
$("#mod-cont-alias").text(alias)
$("#mod-cont-prior").text(priorStar)
$("#mod-cont-desc").text(desc)
$("#mod-cont-pattern").text(pattern)
$(".modal").modal("show")
})
$("#transpose").on("change", e => {
const scaleObj = getScale({
pattern: $("#mod-cont-pattern").text(),
transpose: $(e.target).val()
})
displayScore(scaleObj, $("#mod-cont-name").text(), $("#transpose option:selected").text())
})
$("#change-enharmonic").on("change", e => {
const value = $(e.target).val()
switch (value) {
case "common":
glb_currentName = NAME_COMMON;
break;
case "sharp":
glb_currentName = NAME_SHARP;
break;
case "flat":
glb_currentName = NAME_FLAT;
break;
}
const scaleObj = getScale({
pattern: $("#mod-cont-pattern").text(),
transpose: $("#transpose").val()
})
displayScore(scaleObj, $("#mod-cont-name").text(), $("#transpose option:selected").text())
})
</script>
</body>
</html>

view raw

scale-list.php

hosted with ❤ by GitHub

Transpose, Flat Only 적용

문의 | 코멘트 또는 yoonbumtae@gmail.com




0개의 댓글

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다