2015년 6월 30일 화요일

PL2303 Windows Driver 설치 문제

http://www.ifamilysoftware.com/news37.html

윈도우라 신경 안쓰고 있었는데 PL2303 드라이버가 설치되지 않는다는 소리가 있어서 검색해본 것을 영구보존용으로 글을 남깁니다.

혹시라도 이 사이트가 사라질 수도 있어서 공개링크로 파일만 저장도 해놓았습니다.

http://pan.baidu.com/s/1bntvcV9

PL2303_64bit_Installer.exe
PL-2303_Driver_Installer.exe

두 종류가 있는 데 PL-2303_Driver_Installer.exe 는 32bit용 PL2303_64bit_Installer.exe 가 64bit 용입니다.

공홈에서 고생하시는 일이 없기를 ^^;

2015년 6월 26일 금요일

Meteor 경량 Reactive package - ReactiveVar를 사용해보자.


예전 글인 Session으로 Reactive 실시간 타이머에 대한 성원에 힘입어 추가로 하나 더.
http://meteorpad.com/pad/LybNGiNShhNvjCBPD/timeAgo-reactiveVar 예제를 보자.

나머지는 다 비슷하고 Session 이 아니라 reactive-var package를 사용했다.
meteor add reactive-var 로 추가할 수 있다.

Session과 다른 건 비슷하고 key,val 형식이 아니고 Hot code push를 통해 값을 보존할 수 없으며 Global Scope가 아니다. 사실 이것 때문에 쓴다고 봐도 된다. Session은 사실 클라이언트의 부담을 주기 때문에 무분별하게 사용하는 것은 좋지 않다.

var count = new ReactiveVar(Random.id());

와 같이 직접 선언해주고 count.set(value),  count.get() 으로 사용하면 된다.
눈치채신 분들도 있을지 모르겠는데 http://meteor.github.io/blaze/ 의 Blaze.Var 하고 아주 비슷하다.

http://docs.meteor.com/#/full/reactivevar
내용을 참조하면 된다.
구현도 아주 간단해서
https://github.com/meteor/meteor/blob/master/packages/reactive-var/reactive-var.js#L37
메뉴얼 대신 봐도 될 정도.
그러고보니 예전엔 Deps를 꽤 중요하게 강조했던 때도 있었는데 Tracker로 바뀌면서 이런 패키지를 만들었나보다.

재미있는 것은 Optional로 ReactiveVar를 생성 시 두번째 인자인 equalsFunc 에 function을 정의할 수 있는데 이것이 true를 반환하면 set 할때 Reactive 가 작동하지 않는다.

가볍고 고성능인 패키지이니 Session이 넘쳐난다고 있다면 성능을 높이기 위해 한번 써보자.

2015년 6월 25일 목요일

Meteor - Session을 이용한 살아있는 Reactive 게시 시간(moment 사용)

facebook 같은 SNS를 보면 게시글과 댓글의 시간이 실시간으로 갱신되는데
Meteor를 사용하면 불필요한 DOM을 갱신하지 않고 필요한 부분만 적은 코드로 구현할 수 있습니다.

현재 시간 기준으로 얼마나 시간이 흘렀는지 (ex. 5 minute ago) 보여주려면

moment(기준시간).fromNow()
를 사용하면 됩니다.

helper를 만들어보면
<span>{{timeAgo createdAt}}</span>
....
Template.main.helpers({
  ...
  "timeAgo": function(time) {
    return moment(time).fromNow();
  }
});

이와 같이 구현할 수 있죠.

여기에서 우리는 Template의 helper가 Reactive Computation 대상이며Session이 Reactive Data Source라는 점을 사용합니다. (http://docs.meteor.com/#/full/reactivity)

Reactive Computation 대상인 helper 안에서 Session의 값이 갱신되면 helper를 다시 호출하지 않아도 다시 갱신이 되는 것입니다.

1초마다 특정 Session의 값을 갱신하도록 해봅시다.
onCreated에서 interval을 생성하고 onDestroy에서 제거하도록 하여 불필요한 부담을 줄여줍니다.

Template.main.onCreated(function() {
  Session.set("localtime",1);
  this.interval = Meteor.setInterval(function() {
    Session.set("localtime", Random.id());
  }, 1000);
});

....

Template.main.onDestroyed(function() {
  Meteor.clearInterval(this.interval);
});

단지 localtime 이라는 Session에 1초마다 다른 값이 들어오도록 set하는 것으로 모든 준비가 끝났습니다.
Session은 현재 가지고 있는 것과 같은 값이 들어올 경우 불필요한 갱신을 하지 않으므로 항상 다른 값이 들어오도록 Random.id() (https://github.com/meteor/meteor/tree/devel/packages/random) 를 사용해 중복 값을 피합니다. 이는 uuid 값을 생성하여 매번 다른 값을 받습니다. 예전 버전의 Meteor.uuid() 와 동일합니다.

이제 helper를 수정할 차례입니다.

Template.main.helpers({
  ...
  "timeAgo": function(time) {
    return Session.get("localtime") && moment(time).fromNow();
  }
});

이렇게 Session.get 을 추가해줍니다. 그 결과값은 항상 참이므로 &&(and)연산을 통해 항상 이후의 값을 반환합니다.

실제로 Chrome 브라우저에서 변경되는 순간을 보면
이와 같이 두 번째 li 부분이 점멸하면서 해당 DOM이 갱신되는 것처럼 최소한의 변경만 이루어지는 것을 확인할 수 있습니다.

실제로 구현한 소스는 이곳에서 확인하실 수 있습니다.

2015년 6월 21일 일요일

MongoDB를 사용하지 않는 Meteor Publish(+MQTT)

오래간만에 Meteor 문서를 보던 중 흥미로운 내용을 발견.
publish쪽을 보다 보니 이런게 있더라.
http://docs.meteor.com/#/full/publish_added

Meteor.subscribe한 컬렉션에서 observe를 해보면 added/changed/removed 를 받을 수 있는데
사실은 이게 publish에서 "만들"수 있는 것.
최초 publish 에 들어왔을 때 this.ready()를 한번 해주고 들어올때마다 publish에서 add를 해주는 식이다.
외부 API를 쓴다거나 TCP/UDP 연결해서 얻은 결과물을 Collection 형태로 받을 때 매우 유용하다.

그래서 MQTT pub/sub 연동 예제를 구현해보았다.
http://meteorpad.com/pad/uGoYkgrkWxBbkfJhh/mqttMeteor
test.mosquitto.org 를 바라보고 MQTT 메시지를 수신하고 송신할 수 있다.
구현이 얼마없는 것에 비해 매우 잘 작동하지 않는가!?

MongoDB가 아닌 외부의 비동기 호출을 통해 받은 결과를 넘겨주는 것으로 실제 publish쪽 구현은 아주 간단한데

Meteor.publish("chats", function() {
  var pub = this;

  // async function
  client.on('message', function(topic, message) {
    pub.added("chats", Random.id(), {
      message:message.toString(),
      createdAt: +new Date()
    });
  });
  this.ready();
});

이게 전부다.
subscribe쪽에 collection이 준비되었음을 알리기 위해 ready() (http://docs.meteor.com/#/full/publish_ready)를 호출하고 새로 데이터가 들어오면 added(http://docs.meteor.com/#/full/publish_added)를 사용하는 것이 전부다.
added의 인자는 collection명, 중복구분용 id, 실제저장하고자 하는 객체 이렇게 세가지만 잘 넣어주면 된다.
본 예와는 상관없지만 변경과 삭제의 경우도 마찬가지로 changed, removed를 사용한다.

만일 외부 API를 받아오고자 한다면 http package(meteor add http로 추가)를 추가한 후 client.on 부분을 HTTP.get 같은 걸로 대신하면 된다.

2015년 6월 18일 목요일

history 객체를 사용한 client router 구현

지난 Meteor Meetup에서 iron-router를 써야하냐 말아야하냐에 대한 논의가 있었는데
MDG(Meteor Development Group)에서 딱히 router에 대한 명확한 입장이 없어
SPA(Single Page Application)에서 클라이언트쪽 라우터에 대해 정리해 보려고 한다.
전반적인 내용은 여기에 -> http://html5.clearboth.org/history.html

기존의 Web과 다르게 SPA에선 화면간 이동시 전체 화면을 다시 그리지 않는다.
그래서 기존 독립 실행 어플리케이션(데스크톱 프로그램/모바일 앱 등)처럼 부드럽고 빠른 기능 전환이 가능한데 대신 기존의 뒤로 가기 버튼을 무심코 눌렀을 때 해당 어플리케이션에서 이탈하곤 한다.

그래서 실제로 URL이동을 하지 않아도 기능상 다른 화면으로 이동하였을 경우 임의로 URL을 생성하여 history 객체에 넣는 방식으로 구현한다.
전체적인 그림은 아래와 같은데

단순한 전략이다. push 와 pop.
이동이 일어나는 시점에 history.pushState( <state>, "<title>", "<url>"); 로 쌓아두고 뒤로 가기/앞으로 가기를 브라우저에서 실행할 경우 popstate 이벤트를 처리하면 된다.
window.addEventListener("popstate", function (event) {
  // event.state 를 참조
  // 이 시점은 이미 pop을 완료한 상태이므로 location을 살펴보면 이미 이동한 경로를 가져온다.
});
여기에서 잠시 주목할 것은 popstate는 가장 마지막에 pushState 상태를 가져오는데 최초 페이지 로딩 후 History 객체를 보면
> history
  length: 1
  state: null
}
이와 같이 length는 1이고 state가 null인것을 볼 수 있다.
우리는 원하는 곳으로 보내야하기 때문에 이 부분을 바꿔줘야한다.
미리 pushState를 해놓으면 불필요한 history가 생기므로 같은 문법이지만 history를 생성하지 않는 replaceState( <state>, "<title>", "<url>"); 로 대치해준다.

요약하면 이렇다.

  1. 최초 진입시 URL과 상태를 history.replaceState 로 초기화한다.
  2. 화면 이동시 history.pushState 로 이력을 저장한다.
  3. back/forward 시 popstate 이벤트를 받아서 화면 이동을 구현한다.
여기까지는 사실 쉽다.
나머지는 URL Parsing 부분인데
이쪽에 구현해 놓았다.

가상 경로명을 Session에 넣어서 다루었다.

router.routes = {
  "home": /^\/$/,
  "view": /^\/view\/([^/]*)$/,
};
라우터는 이렇게 만들어 놓고 location.pathname 을 정규식으로 매칭해서 같은 패턴이면 template이름을 key로 사용하게 하였다.
만일 매치에 실패하면 router.notfound 를 사용하도록 하고 정의해주도록 하면 좋다.

Template 구조는 이렇게 구성했다.
홈에서 두 개의 하위 링크로 이동을 구성하는 아주 단순한 구조로

초기화면
Home
A
B
View
A
home

이렇게 진행한다.
라우터를 담당할 템플릿에서 어떤 템플릿을 사용할지는 동적으로 결정해야하므로
{{> UI.dynamic template=getRoute}}
를 사용한다.
getRoute에 표현할 template이름을 helper로 넣으면 된다.
실제로 경로를 이동하는 이벤트가 발생하는 곳에서 preventDefault로 이동을 막고 Session.set('pathname', location.pathname') 하고 pushState를 하면 끝.




*. Meteor 에서 Subscribe 연동 (2회차로?) (https://www.discovermeteor.com/blog/template-level-subscriptions/)

Template 별 subscribe 관리는
onCreated 에  
  this.subscribe("postsView", Session.get("params"));

로 지정하면 onDestoryed 시점에 알아서 정리한다.

Template 안에서는 subscribe완료 여부는

{{#if Template.subscriptionsReady}}
{{/if}}

이렇게 체크 가능.
helpers나 events에서Template.instance().subscriptionsReady() 를 사용하면 Reactive로 사용할 수 있어서 완전 꿀.

2015년 6월 10일 수요일

contenteditable을 사용한 HTML WYSWYG 편집기 제작

contenteditable은 말 그대로 해당 tag의 content를 수정가능한 상태로 만드는 태그인데

https://medium.com/medium-eng/why-contenteditable-is-terrible-122d8a40e480
사실 WYSWYG이 이 글대로 쉬운 작업은 아니다.

최종적으로 Meteor의 reactive data 연동하는 것까지를 목표로 잡아보자.

일단 div[contenteditable=true]를 하나 만들고 테두리를 제거하기 위해

[contenteditable=true] {
  outline: none;
}

를 css에 잡자.
contenteditable의 테두리는 레이아웃을 깨뜨리지 않기 때문에 border를 사용하지 않고 있다는 걸 알 수 있다.

하고보니 버튼 등등이 눌렀을 때 테두리가 생기는 것도 싫어서
* {
  outline: none;
}

[contenteditable=true] {
  outline: solid 1px #cbcbcb;
  padding: 0.5em;
}

이렇게 일단 해보자.

빠르게 버튼을 만들어서 기능을 더해보는데

대부분의 기능은 https://developer.mozilla.org/en-US/docs/Web/API/document/execCommand#Commands 에서 구현할 수 있다.

Bold, 정렬 이런 건 괜찮은데 image가 제일 중요.
손을 좀 건드려야하는데
그 이유는 이미지 실제는 항상 엄청 크기가 큰게 들어올 수 있기 때문에 아무래도 불편하다.
크기를 지정해서 해주면 좋은데 그러려면 그냥 insertImage를 쓰면 안되고
해당 커맨드가 들어왔을 때 createElement를 통해서 image 객체를 만들고 현재 커서 위치에 넣도록 하는게 좋다.

  insertImage : (url)->
    console.log "#{url} insert"
    img = document.createElement 'img'
    img.setAttribute 'src', url
    img.setAttribute 'style', 'width: 50px;'
    if window.getSelection
      sel = window.getSelection()
      if sel.getRangeAt and sel.rangeCount
        range = sel.getRangeAt(0)
        range.deleteContents()
        range.insertNode img

window.getSelection이 있음 현재 위치를 range로 잡고 insertNode에 img 객체를 밀어넣는 식.

이것저것 필요한 거 넣고 link랑 image 주소도 넣어서 일단 여기까지.

#selector
@s=(q)-> document.querySelector q
@ss=(q)-> document.querySelectorAll q

@util=
  insertImage : (url)->
    console.log "#{url} insert"
    img = document.createElement 'img'
    img.setAttribute 'src', s("input.url").value
    img.setAttribute 'style', 'width: 50px;'
    @insertObject img
  insertObject : (obj)->
    #TODO : fuck IE?
    if window.getSelection
      sel = window.getSelection()
      if sel.getRangeAt and sel.rangeCount
        range = sel.getRangeAt(0)
        range.deleteContents()
        console.log 'insert'
        range.insertNode obj

# onContentLoaded
document.addEventListener "DOMContentLoaded", (event)->
  for obj in ss(".cmd")
    obj.addEventListener "click", (event)->
      param = event.target.getAttribute "data-param"
      cmd = event.target.getAttribute "data-cmd"
      if cmd is 'insertImage'
        util.insertImage(param)
      else if cmd is 'createLink'
        document.execCommand 'createLink', false, s("input.url").value
      else
        document.execCommand cmd, false, param
      # fix a style of blockquote
      if cmd is 'indent'
        obj.removeAttribute('style') for obj in ss("#editor>blockquote")

createLink 같은 경우는 target="_black"를 해야할 것 같은데 역시 range.deleteContents()를 제거하고 해야할 것 같다.

이제 중요한 건 어떻게 reactive랑 관계를 생각해봐야하는데
정확하게 보면 편집 중인 대상은 외부에서 변경하지 말아야하는데 상태를 collection에 저장하는게 나을 것 같다.

하다보니 디테일이 꽤 늘어났다. 계속 정리해야;

보다보니 대충 중요한 이슈는 이정도다.

  • Link 와 같이 특정 영역에서 커맨드를 날린 후 별도의 입력값을 받아야하는 경우 Selection(선택영역)이 사라져버리기 때문에 포커스가 이동하기전 Range를 저장하고 입력값을 받고 다시 선택영역을 복구해야한다.
  • 실제로 execCommand가 쓸모없는 경우도 많다. a tag 같은 경우엔 target을 지정해야하는 경우도 있고 image 경우도 마찬가지. 선택영역을 특정 Element로 감싸는 구현도 매우 필요함.
  • 요소를 삽입 후 커서의 위치를 삽입한 요소 다음으로 놓고자 할때 선택영역을 재조정해야한다.
Selection과 Range에 대한 설명을 잘 살펴보면서 구현을 하나하나 해보자.

먼저 선택 영역을 보존하는 것은
range = window.getSelection().getRangeAt(window.getSelection().rangeCount-1);
이런 식으로 Range 객체를 보존한다. document.get
rangeCount는 영역이 없으면 0, 있으면 보통 1이지만 스크립트에서 1이상을 줄 수도 있긴 하다. 오류를 피하기 위해 rangeCount가 있는지 미리 검사하는 걸 잊지 말자.

그 다음으로 복원인데 반드시 기존에 Range들을 모두 제거하고 위에서 저장한 영역을 선택하게 하자.
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
선택영역의 보존/복원이 아마 가장 중요한 테크닉이지 않을까 싶다.

좀 더 깊게 들어가서 Element를 감싸는 경우는 다소 섬세한 조작이 필요하다.
  1. range를 저장한다.
  2. 감쌀 Element를 document.createElement(tagname) 으로 생성한다. link일 경우 element=document.createElement('a')
  3. 생성한 Element에 range.extractContents() 로 뽑아낸 선택영역을 appendChild로 붙여넣는다. 이때 range의 종단점이 사라진다. 주의!
    element.appendChild(range.extractContents()) 와 같이 구현.
  4. insertNode로 변경한 Element를 삽입한다.
    range.insertNode(element);
  5. 3,4를 range.surroundContents(element); 로 한번에 구현할 수도 있다. 대신 IE용 polyfill 구현체는 아직 없는 것으로 보인다.
여기까지 한 뒤 이전에 언급한 removeAllRanges()와 addRange를 사용해 복원하자.
그러면 커서는 삽입한 새로운 Element바로 앞을 가르키고 있을텐데
만일 뒤로 보내고 싶다면 insertNode 직후
range.setStartAfter(window.getSelection().focusNode.nextSibling)
이렇게 현재 선택지점의 다음 요소(nextSibling)를 range의 시작점으로 삼는다.

Medium에선 되지만 다른 라이브러리들에서 잘 안되는 기능 중 하나가
중첩 link나 highlight 같은 기능이다.
즉, 링크가 걸린 지점을 포함한 부분에 또 다시 링크를 걸 경우인 예외상황인데 Medium과 같은 경우는 기본적으로 unlink기능을 사용한다.
(해보니까 medium의 경우 겹친 부분이 unlink였다가도 창전환 후 돌아오면 다시 link로 아이콘이 보인다. 그래도 작동은 unlink로 정삭동작!)

이 경우 고려해야할 사항은 이렇다.
먼저 선택한 영역에 있는 node들이 어떤 것이 있는지를 조사한다.
startContainer와 endContainer의 parentNode 를 보자
scan 순서는 startContainer에서부터 nextSibling 을 반복하여 그것이 endContainer와 같을때까지 루프하면 된다.

테스트 케이스를 작성해보면

aaa bbb ccc -> unlink 가능
aaa bbb ccc -> unlink 시 aa는 여전히 링크로 남음
aaa bbb ccc -> unlink 시 cc는 여전히 링크로 남음
aaa bbb ccc -> 복합 케이스


2015년 6월 7일 일요일

Renoise - 악기도 Sample도 없이 Loop만들기

자가 치료용 음악 작업.

Renoise 3.0.1 시작

손풀기라고 생각하고 텅빈 마음으로 출발하자.

10분이면 충분하겠지.

00 패치에 48khz, 8bit mono짜리 48sample을 만들어보자.

웨이브 폼은 대충 sawtooth 같은 느낌으로


거칠게 그리는 게 맛.

중심이 되는 리프를 어떻게 할까

Phrase Editor라는게 보인다. 열어보자.

Arp-Rising 프리셋이 맘에 드네

오케이 이걸로 결정.

꾹꾹 눌러보니 아스트랄하고 좋구만

esc를 누르고 패턴을 짜다보니 48 step 이랑 맞는 듯. 왜냐 이게 12짜리니까.

12x4개를 한패턴으로 가자

C-C-F-F 하나 가고


A#-A#-D#-D# 이렇게 5도권(circle of fifths) 돌아보자.

마지막에 약간 변화줘서 8개.

48이니까 3의 배수가 되어서 왈츠가 되네.

왈츠 좋지. 댄스음악의 대선배님이시.

메마르고 퍽퍽한 사운드가 좋구나.

손으로 그린 샘플 사운드를 그냥 이렇게 생으로 쓰면 음이 끊어지는게 너무 심해서 듣기 괴로우니까 Volume Envelope를 주자


Sustain을 줬더니 더 안타까운 느낌이다.
Decay를 약하게 줬더니 동그란 느낌이 살아나는게 좋아서 더 심하게 줘보고 싶다.

아유 동그랗다.
많이 들어본 소리는 아니지만 그래서 이 짓을 하는 것이지.

소리는 좋아. 괜찮아. 이왕에 아르페지에이터를 썼는데 딜레이가 빠질 수 없지.
S01 이라는 이름의 첫번째 Send에 Delay를 달자.
역시나 프리셋 Synced 1&3 Flipped를 써서 1,3번째 비트를 타고가게 하자.

딜레이 같은 공간계 이펙트는 여러 채널에서 재활용이 가능하니까 Send에 만들어 놓고
얼만큼 보낼지만 정해주는게 시스템 자원 이용면에서 저렴하다.

Send를 검색해서 -3.00db만 Amount 를 준다.

Loom이나 Final Fantasy도 약간 생각나면서 기분이 좀 나아졌다.

일단 빨리 마무리해야지. 나중에 볶아먹든 지져먹든 뤂 슬라이스를 하든 하고.
본격적으로 작업이 되기 전에 중단해야겠다.
이정도면 북을 치거나 피리를 불거나하는 등의 잼을 하기엔 적당하지뭐. 텅텅 비어있으니까.

계산을 잘못해서 튠이 정튠은 아닌데 뭐 악기가 맞추면 되니까 별 상관없지.

 분량을 위해 대충 복사하고 4마디마다 단음 패턴을 넣어서 마무리.

https://soundcloud.com/spectrick/noau