알라딘MGG와이드바


언리얼4 기본 프로젝트 카메라 코드 비교 개발 이야기

카메라쪽 작업할 일이 있는데 참고삼아서 언리얼4 기본 프로젝트를 생성했을 때 카메라 코드들을 비교해 둔다.

FirstPerson

USpringArmComponent 사용하지 않는다.
UCameraComponent 은 GetCapsuleComponent() 에 SetupAttachment 한다.
USkeletalMeshComponent 는 UCameraComponent 에 SetupAttachment 한다.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ACameraCharacter : public ACharacter
{
UCameraComponent* FirstPersonCameraComponent;
};

ACameraCharacter::ACameraCharacter()
{
// Set size for collision capsule
GetCapsuleComponent()->InitCapsuleSize(55.f, 96.0f);

// Create a CameraComponent
FirstPersonCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
FirstPersonCameraComponent->SetupAttachment(GetCapsuleComponent());
FirstPersonCameraComponent->SetRelativeLocation(FVector(-39.56f, 1.75f, 64.f)); // Position the camera
FirstPersonCameraComponent->bUsePawnControlRotation = true; // ThirdPerson 와 동일.

Mesh1P = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("CharacterMesh1P"));
Mesh1P->SetupAttachment(FirstPersonCameraComponent);
};

void ACameraCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput); // 마우스 제어
PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput); // 마우스 제어
}

ThirdPerson

USpringArmComponent* CameraBoom 을 생성해 RootComponent 에 SetupAttachment 한다.
UCameraComponent* FollowCamera 생성해 CameraBoom 에 SetupAttachment 한다.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ACameraCharacter : public ACharacter
{
class USpringArmComponent* CameraBoom;
class UCameraComponent* FollowCamera;
};

ACameraCharacter::ACameraCharacter()
{
// Create a camera boom (pulls in towards the player if there is a collision)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character
CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller

// Create a follow camera
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
}

void ACameraCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput); // 마우스 제어
PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput); // 마우스 제어
}

TopDown

PlayerInputComponent->BindAxis "Turn", "LookUp" 처리가 빠져있어서 APawn::AddControllerYawInput, APawn::AddControllerPitchInput 가 호출되지 않는다.
즉, Controller 의 Yaw, Pitch 가 변화하지 않고, CameraBoom->bUsePawnControlRotation = true; 처리도 없으므로 카메라가 회전하지 않는다.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ACameraCharacter : public ACharacter
{
class UCameraComponent* TopDownCameraComponent;
class USpringArmComponent* CameraBoom;
};

ACameraCharacter::ACameraCharacter()
{
// Create a camera boom...
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->SetUsingAbsoluteRotation(true); // Don't want arm to rotate when character does (TopDown 에서만 호출)
CameraBoom->TargetArmLength = 800.f;
CameraBoom->SetRelativeRotation(FRotator(-60.f, 0.f, 0.f)); // TopDown 에서만 호출
CameraBoom->bDoCollisionTest = false; // Don't want to pull camera in when it collides with level <- TopDown 에만 있다.

// Create a camera...
TopDownCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("TopDownCamera"));
TopDownCameraComponent->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
TopDownCameraComponent->bUsePawnControlRotation = false; // Camera does not rotate relative to arm



Cubase 드럼맵 파일을 drm <-> text 형태로 변환하는 파이썬 코드 개발 이야기

올해 7월에 http://parkpd.egloos.com/4186280 에서 이미 비슷한 코드를 만들었는데 이걸 왜 또 만들었냐면,
  • 정말 7월에 저런 코드를 만들었다는 걸 까먹었기 때문이고...(뭔가 기시감은 있었는데)
  • 좀 더 향상된 기능이 필요했기 때문이다.
이전에는 drm 파일을 text 파일로 변경만 가능했다면, 이제는 이렇게 변경된 text 포멧으로부터 drm 파일을 생성할 수 있다.

NI 에서 만든 STUDIO DRUMMER 를 쓰려고 보니 drm 파일이 부정확한 게 이 코드를 만들게 된 첫 번째 이유다.

|| C1 || KICK Dampened ||
|| C#1 || SNARE Side Stick ||
|| D1 || SNARE Centre Alt ||
으로 표현된 텍스트 파일에서 원하는 미디 노트와 드럼 키 이름을 입력한 뒤에 이 파일을 실행인자로 넘기면 txt 파일에서 drm 파일을 생성할 수 있다.
이 작업을 하면서 middle C 가 프로그램마다 왜 다르게 되었는지도 대강 알게 되었다.
(Cubase 는 C4 가 72, AKAI 는 C4 가 60)

Visual Studio 2019 에 Python 용 unittest 프레임워크를 이용하면 Run Tests 를 할 수 있다는 것도,
"Use adaptive formatting" 옵션이 켜져 있으면 tab 을 자꾸 space 로 바꿀 가능성이 있다는 것도 알게 되었다.

import os
import pathlib
import sys, getopt
from bs4 import BeautifulSoup

# Middle C on a piano is C4
# midi note 36 이 Cubase 에서는 C1 인데 AKAI 에서는 C2 다.
# Roland(AKAI) 는 middle c(60) 를 C4 로 쓰고, Yamaha(Cubase) 는 C3 로 쓴다.
# 역사적으로는 Roland C4 가 표준인 거 같지만, 내가 쓰는 대부분의 소프트웨어가 C3 를 쓰므로
# 여기에서는 C3 를 기본값으로 한다.
def get_octave_modifier(using_middle_c_c4):
if using_middle_c_c4 :
return 1
else:
return 2

def convert_num_to_note(inote, using_middle_c_c4):
note_array = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
octave, interval = divmod(inote, 12)
ret = note_array[interval] + str(octave - get_octave_modifier(using_middle_c_c4))
return ret

def split_note_octave(inote_str):
split_index = 1
if inote_str[1] == "#":
split_index = 2
note_str = inote_str[0:split_index]
octave_str = inote_str[split_index:]
return note_str, octave_str

def convert_note_to_num(inote_str, using_middle_c_c4):
note_array = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
note_str, octave_str = split_note_octave(inote_str)
octave = int(octave_str)
note_index = note_array.index(note_str)
return (octave + get_octave_modifier(using_middle_c_c4)) * 12 + note_index

def get_file_name_without_extension(file_path):
file_name = os.path.basename(file_path)
return os.path.splitext(file_name)[0]

def save_drm_as_txt_file(file_path_without_ext, item_list, using_middle_c_c4):
with open(file_path_without_ext + ".txt", "w") as txt_file:
for item in item_list:
note_name = item.find("string")["value"]
inote_value_str = item.find("int", {"name": "INote"})["value"]
inote_value = int(inote_value_str)
str = "|| " + convert_num_to_note(inote_value, using_middle_c_c4) + " || " + note_name + " ||"
print(str)
txt_file.writelines(str + "\n")

def convert_drm_to_txt(file_path):
file_path_without_ext, _ = os.path.splitext(file_path)
with open(file_path_without_ext + ".drm", "r") as xml_file:
soup = BeautifulSoup(xml_file, 'lxml')
item_map = soup.find("list", {"name": "Map"})
item_list = item_map.find_all("item")

save_drm_as_txt_file(file_path_without_ext + "_roland_middle_c4", item_list, True)
save_drm_as_txt_file(file_path_without_ext, item_list, False)

def convert_txt_to_drm(file_path, using_middle_c_c4):
file_path_without_ext, _ = os.path.splitext(file_path)
file_name = get_file_name_without_extension(file_path)
with open(file_path_without_ext + ".txt", "r") as txt_file, open(file_path_without_ext + ".drm", "w") as xml_file :
xml_start = """<?xml version="1.0" encoding="utf-8"?>
<DrumMap>
<string name="Name" value="%s" wide="true"/>
<list name="Quantize" type="list">
<item>
<int name="Grid" value="4"/>
<int name="Type" value="0"/>
<float name="Swing" value="0"/>
<int name="Legato" value="50"/>
</item>
</list>
<list name="Map" type="list">"""
xml_file.write(xml_start % file_name)

# txt 파일을 읽어서 xml 으로 변환한다.
# 1 부터 127까지 비어있으면 큐베이스의 Pitch 값이 밀려서 표시되므로 꽉 채워줘야 한다.
list_to_name = ["" for x in range(128)]

# .txt 파일에서 로딩한 데이터를 list_to_name 에 덮어쓴다.
lines = txt_file.readlines()
for line in lines:
splited_data = line.split("||")
list_to_name[convert_note_to_num(splited_data[1].strip(), using_middle_c_c4)] = splited_data[2].strip()

xml_item = """\n <item>
<int name="INote" value="{0}"/>
<int name="ONote" value="{0}"/>
<int name="Channel" value="-1"/>
<float name="Length" value="200"/>
<int name="Mute" value="0"/>
<int name="DisplayNote" value="{0}"/>
<int name="HeadSymbol" value="0"/>
<int name="Voice" value="0"/>
<int name="PortIndex" value="0"/>
<string name="Name" value="{1}" wide="true"/>
<int name="QuantizeIndex" value="0"/>
</item>"""

# 0~127 까지 xml node 'item' 를 생성한다.
for i in range(0, 128):
print(str(i) + " " + list_to_name[i])
xml_file.write(xml_item.format(i, list_to_name[i]))

xml_file.write("""\n </list>
<list name="Order" type="int">\n""")

for i in range(0, 128):
xml_file.write(""" <item value="{0}"/>\n""".format(i))

xml_file.write(""" </list>
<list name="OutputDevices" type="list">
<item>
<string name="DeviceName" value="Default Device"/>
<string name="PortName" value="Default Port"/>
</item>
</list>
</DrumMap>""")

def main(file_path):
file_ext = pathlib.Path(file_path.lower()).suffix;

# 확장자에 따라 .drm <-> .txt 로 변환한다.
if file_ext == ".drm" :
convert_drm_to_txt(file_path)
elif file_ext == ".txt" :
convert_txt_to_drm(file_path, False)

if __name__ == '__main__':
main(sys.argv[1])
# main("NI SD Garage Full 2.txt")
# main("NI SD Garage Full 2.drm")
# main("C:\\Music\\CubaseDrumMap\\Studio Drummer\\NI SD Garage Full 2.drm")
# main("C:\\Music\\CubaseDrumMap\\Studio Drummer\\NI SD Garage Full 2.txt")


import DrumMap
import unittest

class CustomTests(unittest.TestCase):
def test_convert_num_to_note(self):
self.assertEqual(DrumMap.convert_num_to_note(0, False), "C-2")
self.assertEqual(DrumMap.convert_num_to_note(1, False), "C#-2")
self.assertEqual(DrumMap.convert_num_to_note(36, False), "C1") # Yamaha, Cubase
self.assertEqual(DrumMap.convert_num_to_note(36, True), "C2") # Roland, AKAI

def test_convert_note_to_num1(self):
note_str, octave_str = DrumMap.split_note_octave("C#4")
self.assertEqual(note_str, "C#")
self.assertEqual(octave_str, "4")

def test_convert_note_to_num2(self):
self.assertEqual(DrumMap.split_note_octave("C0"), ("C", "0"))
self.assertEqual(DrumMap.split_note_octave("C#10"), ("C#", "10"))
self.assertEqual(DrumMap.split_note_octave("C#-2"), ("C#", "-2"))
self.assertEqual(DrumMap.split_note_octave("E4"), ("E", "4"))
self.assertEqual(DrumMap.split_note_octave("F#3"), ("F#", "3"))

def test_convert_note_to_num(self):
self.assertEqual(DrumMap.convert_note_to_num("C-2", False), 0)
self.assertEqual(DrumMap.convert_note_to_num("C0", False), 24)
self.assertEqual(DrumMap.convert_note_to_num("C#0", False), 25)
self.assertEqual(DrumMap.convert_note_to_num("C1", False), 36)
self.assertEqual(DrumMap.convert_note_to_num("C10", False), 144)
self.assertEqual(DrumMap.convert_note_to_num("C#10", False), 145)

def test_convert_middle_c(self):
self.assertEqual(DrumMap.convert_note_to_num("C4", True), 60) # Roland
self.assertEqual(DrumMap.convert_note_to_num("C3", True), 48) # Roland, AKAI
self.assertEqual(DrumMap.convert_note_to_num("C2", True), 36) # Roland, AKAI
self.assertEqual(DrumMap.convert_note_to_num("C3", False), 60) # Yamaha, Cubase
self.assertEqual(DrumMap.convert_note_to_num("C4", False), 72) # Yamaha, Cubase
self.assertEqual(DrumMap.convert_note_to_num("C1", False), 36) # Yamaha, Cubase

def test_get_file_name_without_extension(self):
self.assertEqual(DrumMap.get_file_name_without_extension("C:\\Music\\CubaseDrumMap\\Studio Drummer\\NI SD Garage Full 2.drm"), "NI SD Garage Full 2")
self.assertEqual(DrumMap.get_file_name_without_extension("NI SD Garage Full 2.drm"), "NI SD Garage Full 2")

if __name__ == '__main__':
unittest.main()

이제는 다시 미디 찍으러 가자.

python 으로 이더넷 mac 주소 얻는 코드 개발 이야기

https://pypi.org/project/get-mac/

import getmacprint(getmac.get_mac_address())

https://pypi.org/project/netifaces/

import netifacesfor nic in netifaces.interfaces():    iface = netifaces.ifaddresses(nic)[netifaces.AF_LINK]    if len(iface[0]['addr']) > 0:        print("mac address: ", iface[0]['addr'])

Waves 라는 가상악기가 MAC address 를 비교해 인증하고 있는데, Hyper-V 가 가상 이더넷 어뎁터를 설치했더니 인증에 실패하고 있다. Waves 기술지원팀과 논의해서 이 문제를 해결하려고 Python 코드를 간단히 짜 봤다.


Python 으로 펜타토닉 스케일 위치 구하기 개발 이야기

얼마 전에 기타 솔로가 어떤 펜타토닉 스케일로 되어 있는지를 알고 싶어서
플랫보드를 프린터해서 노트를 손으로 그려가며 펜타토닉 CAGED 모양을 비교해서 겨우 G minor 를 찾았다.


찾고 나서 보니 b 가 2개 붙어 있는 곡이라 Bb 장조 혹은 G 단조일테니 G minor 펜타토닉부터 찾아보면 빨랐겠지만
조성을 모른 상태로 펜타토닉 스케일을 좀더 쉽게 찾을 방법이 없을까 해서 파이썬으로 간단하게 만들어 보았다.
솔로 노트를 있는대로 입력하면 12개의 마이너 스케일과 얼마나 유사한지를 출력해 준다.
예외 처리 같은 건 생략한다. 나중에는 모드나 코드톤 같은 것도 분석해 주면 좋을 거 같다.
(여기까지 쓰고 보니 Guitar Pro 에 이런 비슷한 기능이 이미 있을 거 같다.)
(그리고 어느 정도 수준에 이르면 귀로 듣자마자 스케일을 알 수 있겠지. 이런 코드를 계속 쓰고 있으면 사실 안 되는 거다.)

from itertools import combinations

# A C D E G(0, 3, 5, 7, 10)
pentatonics = [0, 3, 5, 7, 10]
note_array = ['A', 'Bb', 'B', 'C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab']

# 입력값을 정리한다. (중복 제거, sort)
def GetInput():
while True:
input_str = input("Input 4~7 different notes as number. ex. A C D E Gb\ninput: ")
input_notes = [x for x in input_str.split(' ')] # 입력값을 list 로 만들기
input_notes = list(dict.fromkeys(input_notes)) # 중복 제거
input_notes.sort()
if len(input_notes) >= 4 and len(input_notes) <= 7:
return input_notes
else:
pass

# 12 개의 minor 펜타토닉 데이터를 준비한다.
def ReadyPentatonicData():
pentatonic_notes = []
for i in range(12):
p = [(a + i) % 12 for a in pentatonics]
p.sort()
p_note = [note_array[a] for a in p]
pentatonic_notes.append(p_note)
return pentatonic_notes

# 유사도 구하기
def CalcSimilarity(input_notes, index, penta_data):
input_notes_comb = combinations(input_notes, min(len(input_notes), 5)) # 최대 5개의 조합을 찾은 뒤
max_sum = -1
max_sum_list = []
for in_notes in list(input_notes_comb):
s = sum([1 for i, j in zip(in_notes, penta_data) if i == j]) # 노트가 몇 개 일치하는가 구하고 가장 많이 맞는 값을 출력한다.
if s > max_sum:
max_sum = s
max_sum_list = in_notes
print(note_array[index], "minor:", penta_data, " input:", max_sum_list, "Similarity:", max_sum / 5.0 * 100.0, '%') # 같은 아이템이 몇 개 있는가

def main():
input_notes = GetInput()

pentatonic_notes = ReadyPentatonicData()

for index in range(len(pentatonic_notes)):
penta_data = pentatonic_notes[index]
CalcSimilarity(input_notes, index, penta_data)

if __name__ == "__main__":
main()


결과는 다음과 같다.


Python 으로 타이타닉 승객의 생존율 구하기 개발 이야기

요근래 새로 파이썬 공부하면서 알게 된 문제. 찾아보니까 Kaggle 관련해서 이미 유명한 문제인 거 같다.

타이타닉 승객 데이터 csv 데이터는 여기에서 찾을 수 있다.
https://raw.githubusercontent.com/TeamLab/machine_learning_from_scratch_with_python/master/code/ch12/titanic/train.csv

1, 2, 3 등석에 탑승한 남, 여 승객은 각각 몇 % 생존했는가를 구하는 코드다.

import numpy as np
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/TeamLab/machine_learning_from_scratch_with_python/master/code/ch12/titanic/train.csv')
print(df.pivot_table(index='Sex', columns='Pclass', values='Survived', aggfunc=np.mean), "\n")

# Pclass 1 2 3
# Sex
# female 0.968085 0.921053 0.500000
# male 0.368852 0.157407 0.135447


생존률이 여성은 96%, 92%, 50%, 남성은 36%, 15%, 13% 라고 한다.
처음 타이타닉 영화 봤을 때는(그게 1997년이었네. 아련하다.) 어떤 기분이었는지는 기억이 안 나는데
지금은 세월호 때문에 이 영상을 보고 있기가 힘들다.
죽음을 눈앞에 두고도 의연한 모습을 보인 그 시대 사람들에게 존경을 보낸다.

1 2 3 4 5 6 7 8 9 10 다음


Yes24위대한게임의탄생3

위대한 게임의 탄생 3
예스24 | 애드온2