음악, 삶, 개발

ValueTree 공부하기 파트2 : 연습 본문

개발 공부/Juce 공부방

ValueTree 공부하기 파트2 : 연습

Lee_____ 2020. 10. 26. 02:36

앞서 이론을 배웠으니, 실제 ValueTree 에 요소들을 추가, 삭제하는등에 대해

연습문제를 풀면서 해보겠다.


< 연습 1 : ValueTree 만들기 >

문제

< Note />

정답

juce::Identifier treeName {"Note"};
juce::ValueTree tree {treeName};

설명

 

setProperty 와 헷깔리지말자.

setProperty 는 말그대로 이미 생성된 ValueTree 에 Property 를 추가 또는 변경하는것이다.

ValueTree 의 이름은 반드시 초기화할때 인자로 넘겨야한다.

또한 초기화후 해당 ValueTree 의 이름은 절대 변경될수없다.


< 연습 2: property 추가하기 > 

 

문제

< Note pitch = 64 > 

정답

const juce::Identifier treeName {"Note"};
const juce::Identifier pitch {"pitch"};

juce::ValueTree tree {treeName};
tree.setProperty(pitch, 64, nullptr);

< 연습 3 : 여러개의 property 추가하기 >

 

문제 

< Note id = "ThisIsNoteId" pitch = 36 velocity = 64 start = 0.1 end = 0.5 active = true > 

정답

const juce::Identifier treeName {"Note"};
const juce::Identifier id {"id"};
const juce::Identifier pitch {"pitch"};
const juce::Identifier velocity {"velocity"};
const juce::Identifier start {"start"};
const juce::Identifier end {"end"};
const juce::Identifier active {"active"};

juce::ValueTree tree {treeName};

tree.setProperty(id, "ThisIsNoteId", nullptr);
tree.setProperty(pitch, 36, nullptr);
tree.setProperty(velocity, 64, nullptr);
tree.setProperty(start, 0.1, nullptr);
tree.setProperty(end, 0.5, nullptr);
tree.setProperty(active, true, nullptr);

설명

 

이 예제가 중요한것은,

setProperty 의 2번째 인자가 var 객체인데, 

어떠한 값이든 리터럴로 넘길수있다는것이다.

"ThisisNoteId"juce::String 이다.

36int 이다.

0.1double 이다.

truebool 이다.


< 연습 4 - property 를 추가하고, 수정하기 >

문제

< Note pitch = 36 > // property 추가
< Note pitch = 64 > // property 수정

정답

const juce::Identifier treeName {"Note"};
const juce::Identifier pitch {"pitch"};

juce::ValueTree tree {treeName};

tree.setProperty(pitch, 36, nullptr); // property 추가
tree.setProperty(pitch, 64, nullptr); // property 수정

설명

 

setProperty 는 property 를 추가, 수정하는데에 둘다 사용된다

만약 내가 제공하는 juce::Identifier 가 해당 tree 에 없다면,

property 는 추가되고, 이미 존재한다면 property 가 수정된다.


< 매우 중요!!!!!!!!!!!!!!!!!!!!!

< 연습 4 - property 의 값을 가져오기 (var 에 대해 이해하기!) >

 

문제 : ValueTree 의 pitch property 를 출력하라!

 

정답

const juce::Identifier treeName {"Note"};
const juce::Identifier pitch {"pitch"};

juce::ValueTree tree {treeName};

tree.setProperty(pitch, 36, nullptr); // property 추가

// 가져오기
juce::var p1  = tree.getProperty(pitch);
int p2        = tree.getProperty(pitch); // also vaild!

DBG(p2); // 36

설명 

getProperty(const juce::Identifier& id) 를 사용한다.

이 함수는 var 객체를 return 하는데, 이때 매우 중요한것이 있다.

해당 property 의 타입에 맞는 타입의 객체로 assignment 할수있다는것이다.

따라서 아래와 같은 코드가 valid 이다.

int p2 = tree.getProperty(pitch); // also vaild!

물론 var 객체에 이런 저런 type 을 역으로 집어넣는것도 가능하다.

juce::var v1 = 1;           // int
juce::var v2 = 0.2f;        // float
juce::var v3 = 0.5;         // double
juce::var v4 = false;       // bool
juce::var v5 = "Hello";     // juce::String

이런 유연한(?) 대입들이 가능한것은 var 클래스안에,

각 primitive type 들에 대한 operator= 가 정의되어있기때문이다.

var 클래스 공식문서중 = 연산자 오버로딩

var 는 굉장히 JavaScript 의 var 같은 녀석이다. type 의 구애를 받지않는것이다.

따라서 아래와 같은 것도 가능하다.

juce::Identifier treeName       {"tree"};
juce::Identifier propertyAny    {"any"};

juce::ValueTree tree {treeName};

int             v1 = 1;
float           v2 = 0.5f;
double          v3 = 0.9;
bool            v4 = false;
juce::String    v5 = "Hello";

tree.setProperty(propertyAny, v1, nullptr); // Now property is int.
tree.setProperty(propertyAny, v2, nullptr); // Now property is float.
tree.setProperty(propertyAny, v3, nullptr); // Now property is double.
tree.setProperty(propertyAny, v4, nullptr); // Now property is bool.
tree.setProperty(propertyAny, v5, nullptr); // Now property is juce::String

property 가 기존에 bool 이었어도, 다시 setProperty 할때 int 또는 double 또는 juce::String 이 될수있는것이다.

이런 유연함이 좋을수도, 독이 될수도있다.

굉장히 C++ 스럽지않기때문이다.

유연함과 엄격함의 적절한 조화를 찾을지, C++ 의 엄격함을 유지해야할지 잘 선택해야할거같다.


< 연습 5 - ValueTree 안에 ValueTree 집어넣기 >

문제

<Parent>
  <Child>
    <GrandChild/>
  </Child>
</Parent>

정답 

juce::Identifier    parentNodeName      {"Parent"};
juce::Identifier    childNodeName       {"Child"};
juce::Identifier    grandChildNodeName  {"GrandChild"};

juce::ValueTree     parentNode          {parentNodeName};
juce::ValueTree     childNode           {childNodeName};
juce::ValueTree     grandChildNode      {grandChildNodeName};

parentNode  .appendChild(childNode, nullptr);
childNode   .appendChild(grandChildNode, nullptr);

 

설명 

 

appendChild 를 실행하면 vectorpush_back 처럼 부모 tree 의 자식 tree 들중 가장 뒤로 해당 tree 를 집어넣게된다.

만약 특정 위치에 넣고싶다면

void addChild (const ValueTree& child, int index, UndoManager *undoManager)

를 사용해야한다.

 

아래의 두 코드는 동일하다.

parentNode.appendChild(childNode, nullptr);  // 뒤에 삽입
parentNode.addChild(childNode, -1, nullptr); // 뒤에 삽입 : -1 은 가장 마지막을 나타냄.

< 연습 6 - Sequncer 안에 미디노트를 표현해보기 >

 

코드

using Id = const juce::Identifier;

Id sequencerId  {"sequencer"};
Id noteId       {"note"};

juce::ValueTree sequencer   {sequencerId};
juce::ValueTree note1       {noteId};
juce::ValueTree note2       {noteId};
juce::ValueTree note3       {noteId};
juce::ValueTree note4       {noteId};

sequencer.appendChild(note1, nullptr);
sequencer.appendChild(note2, nullptr);
sequencer.appendChild(note3, nullptr);
sequencer.appendChild(note4, nullptr);

출력