JDTuner
-
완성 및 마무리JDTuner/개발기록 2026. 5. 4. 20:14
데모 영상https://youtu.be/bxciA4DR3jc [왼쪽 : 튜너앱 / 오른쪽 : GarageBand 튜너] 테스트 진행방식튜너앱을 실행하고 기타를 오디오 인터페이스를 통해 맥북과 연결해 GarageBand의 튜너와 동시에 측정 결과둘을 비교했을때 낮을땐 낮게 높을땐 높게 측정되는 경향은 일치하지만 세밀한 수치에서 약간씩 차이가 나는데 앱에서의 수치가 좀 더 높게 나오는거로 보인다. 수치가 차이나는 이유에 대한 생각측정 데이터 성질의 차이튜너앱은 디바이스의 마이크를 통해 들어오는 소리로 측정하고GarageBand는 픽업 신호가 들어가기 때문에 둘의 차이가 있다고 생각 마찰음 및 기타 픽업의 영향줄을 피킹할때의 마찰음 때문에 그럴수도 있고데모에 사용된 기타 픽업이 높은음을 좀 걸러준다던지 등 영..
-
튜닝 모드 변경JDTuner/개발기록 2026. 4. 23. 15:46
환절기에 골골대느라 2주를 날렸다.꽃가루 알레르기, 비염, 감기 난리가 해가 갈수록 심해진다... 이 프로젝트의 마지막으로 추가할 기능인 튜닝 모드 변경 기능을 만들어봤다. 대상 주파수의 외부 주입TunerResult JDTuner::getGuitarTunerResult(float frequency){ if (frequency 500.0f) return {result}; // 1. 기타 6줄의 표준 주파수 배열 float guitarStrings[] = {82.41f, 110.00f, 146.83f, 196.00f, 246.94f, 329.63f}; const char *guitarNoteNames[] = {"6E", "5A", "4D", "3G", "2B", "1E"}; // 이 주..
-
프로젝트 구조 변경 - 튜너를 외부로 분리JDTuner/개발기록 2026. 4. 3. 20:19
변경 전 구조 위 이미지는 변경 전 구조이다.UI를 제외한 나머지 레이어의 역할을 설명하면DSP Engine Layer주파수 검출을 위한 Yin 알고리즘을 실행시키고 Wrapper로 결과를 전달한다.Wrapper Layer튜너로 부터 받은 결과 값을 WrapperResult로 변경해 UI로 전달한다.이 과정에서 cents값의 제한을 두게 하거나smoothFrequency와 같은 데이터에 관여하는 코드가 포함되어 있다. 문제점 1 - Wrapper에서의 불필요한 코드Wrapper는 c++에서 받은 값을 Swift로 전달하는 역할만 가지고 있어야 한다고 생각했다.비즈니스 로직과 같은 데이터를 다루는 코드가 들어갈 경우 튜너 프로젝트와 Wrapper 둘다 수정해야하고c++, Objective-C 간의 호환..
-
UI 수정JDTuner/개발기록 2026. 3. 15. 17:30
개선 이전개선 전 디자인은 숫자 밖에 모르는 공돌이 인간이 만든 테스트용 화면 같다.출시까지 고민하고 있는 만큼 조금 더 상용 앱 같은 느낌으로 수정하고 싶었다. 후보군 사실 위에서말한 숫자밖에 모르는 공돌이 인간이 나임.그래서 제미나이에게 디자인 개선안을 물어봤다. 1번미니멀리즘을 추구하는 나는 정말 마음에 드는 디자인이다.군더더기 없이 깔끔하고 이후에 기능 추가하기에도 좋아보인다. 2번6현 기타의 헤드가 전부 저런 모양이 아니며이후 7~8현 기타의 튜닝도 만들어보고 싶은 생각이 들어 패스 3번그래프와 슬라이드 전부 좋았지만렌더링 리소스가 너무 많이 들어서 배보다 배꼽이 더 커보였다.그냥 빨리 튜닝하고 다른거 해야할건데 이거 때문에 핸드폰이 뜨거워지면 안될거 같았다그래서 패스 최종 선택 - 1번 디자인..
-
정확도 개선 2 - 1차 IIR 필터JDTuner/개발기록 2026. 3. 11. 20:51
문제점마이크를 사용하는 앱이므로 어지간하면 조용한곳에서 사용하겠지만마이크로 들어가는 소리는 단순히 기타 줄의 소리만 들어가는게 아니다.1. 배음기타 줄에서 나오는 파형은 여러개의 파형의 합쳐져 있다.6번 줄(82.4Hz)을 예로 들면,정배수인 164.8Hz(2배음), 247.2Hz(3배음), 329.6Hz(4배음) 등의고음역대 소리가 동시에, 치는 순간에는 원래 음보다 더 강하게 튀어나온다. 2. 피킹음피킹시 피크(Pick)가 줄을 긁는 고음역대의 쇳소리도 들어갈 수 있다. 3. 주변 소음TV를 틀어 놓거나 하는 주변 소음도 어느정도 있을 수 있다. 해결방식 - 1차 IIR 필터이런 여러소리가 같이 들어가는 상황에서 최대한 기타 줄의 음역대의 영역으로 가져오기 위해필터가 필요하고 이럴때 간단한 로우패스 ..
-
정확도 개선 1 - Parabolic InterpolationJDTuner/개발기록 2026. 3. 11. 20:02
문제점 현재의 pitchTau은 int 타입으로 정수값으로만 계산된다.따라서 V 형태로 정수 사이값이 고려되지 않은 선형적으로 계산된 값이고 자연스럽지 않다.그래서 tau값 기준으로 -1, +1을 포함한 3개의 값을 지나는 포물선을 가정하고 최소값 위치를 구하는Parabolic Interpolation이 필요하다. 이론 위의 포물선이 있다고 가정하고 현재의 tau값을 0이라고 가정하고 양 옆 점이 위와 같다고 하면 위 식을 이용해 연립방정식을 계산하면 c는 베타이기 때문에 위의 결과가 나온다. 식 A와 B를 더하면 a, 빼면 b를 구할 수 있다. 맨 처음 가정한 포물선 방정식을 이용하고 이를 미분해서 최소값이 나오는 x 값을 구하면 위와 같다. 앞서 구한 a와 b 값을 대입하면 x 값을 구할 수 있고..
-
구조 개선 2 - 비동기스트림JDTuner/개발기록 2026. 3. 10. 20:55
현재 구조 및 문제점 현재 구조는 튜너쪽에서 계속 오디오 입력을 받아 결과를 계산하고뷰쪽에서 Timer를 통해 0.1초마다 Wrapper를 통해 튜너의 결과를 사용하는 방식이다.private let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()// ....onReceive(timer) { _ in guard let wrapperResult = wrapper.getTunerResult() else { return } result = wrapperResult} 튜너의 기능을 본격적으로 사용하고 있든 말든뷰쪽에서는 Timer로 계속 호출해 낭비가 생긴다고 판단해튜너에서 값이 실제로 업데이트 될때마다 뷰를 업데이트 하는 방식으..
-
구조 개선 1 - 데이터 관리 + 테스트 영상JDTuner/개발기록 2026. 3. 9. 20:54
문제점- (float)getFrequency { return engine->getResult().frequency;}- (NSString *)getNoteName { TunerResult res = engine->getResult(); return [NSString stringWithUTF8String:res.noteName.c_str()];}- (float)getCents { return engine->getResult().cents; 현재 Wrappper쪽 코드를 보면 튜너 결과값의 각 프로퍼티 당 메서드 하나를 사용해 사용하고 있다.튜너쪽 c++ TunerResult 구조체의 변동에 따라 이쪽 코드를 유지보수하기 까다로워보여Swift쪽에서도 데이터들을 하나로 묶어서 관리할 방법..