<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>JD-man 개발기록들</title>
    <link>https://jd-man.tistory.com/</link>
    <description>개발기록들을 남기려고 합니다</description>
    <language>ko</language>
    <pubDate>Sun, 10 May 2026 22:46:19 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>JD-man</managingEditor>
    <item>
      <title>완성 및 마무리</title>
      <link>https://jd-man.tistory.com/82</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;데모 영상&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/bxciA4DR3jc&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/bxciA4DR3jc&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=bxciA4DR3jc&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/1vPvJ/dJMb8Xki8T5/NO5WIYB02JY9ggAgEeuas0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/yunIY/dJMb86n1l7g/AXqbOFD8H44CZvHDKlbAQk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bLt6bK/dJMb85WW7cE/MeoOrrHodcnDyRgkVa9350/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;DH J&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/bxciA4DR3jc&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[왼쪽 : 튜너앱 / 오른쪽 : GarageBand 튜너]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 진행방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너앱을 실행하고 기타를 오디오 인터페이스를 통해 맥북과 연결해 GarageBand의 튜너와 동시에 측정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘을 비교했을때 낮을땐 낮게 높을땐 높게 측정되는 경향은 일치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 세밀한 수치에서 약간씩 차이가 나는데 앱에서의 수치가 좀 더 높게 나오는거로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수치가 차이나는 이유에 대한 생각&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;측정 데이터 성질의 차이&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너앱은 디바이스의 마이크를 통해 들어오는 소리로 측정하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GarageBand는 픽업 신호가 들어가기 때문에 둘의 차이가 있다고 생각&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;마찰음 및 기타 픽업의 영향&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;줄을 피킹할때의 마찰음 때문에 그럴수도 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데모에 사용된 기타 픽업이 높은음을 좀 걸러준다던지 등 영향을 받았을 수도 있을거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터 후처리의 영향&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;소수점에서의 파악을 위한 Parabolic Interpolation 단계나 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부드러운 애니메이션을 위한 smooth 처리 같은 후처리 단계의 영향이 있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 세밀한 보간법이나 데이터에 영향을 주지 않는 부드러운 애니메이션 방법이 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;불안정한 수치에 대한 생각&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너앱의 경우 마이크를 통한 데이터를 사용하다보니 주변 소음에 많이 취약하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대한으로 조용한 환경에서 테스트 했음에도 튀는 수치들이 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조용한 환경 사용해야한다는 점 자체가 우선 큰 제약이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JUCE가 아니었어도 됐는지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금와서 생각해보면 JUCE의 DSP 관련 코드를 사용하지 않았고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크 인풋 데이터를 가져오기 위해서만 사용했기 때문에 JUCE가 필요했나 생각이든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 데이터 흐름은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;engine -&amp;gt; Tuner -&amp;gt; engine -&amp;gt; App&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AVFoundation이나 AVAudioSession 같은거 썼다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUCE가 없으니 engine은 필요가 없으므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App -&amp;gt; Tuner -&amp;gt; App의 간단한 구조가 됐지 않았을까 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 앱은 UI를 위해서만 사용하고 싶고 그러면 소리 관련 코드가 없어야 하니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUCE 사용은 충분히 의미가 있었다고 생각된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;DSP보단 iOS에 집중된 느낌&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;튜너 기능을 구현하기 위한 코드보다는&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기능별로 분리하기 위한 레이어 설계라던가&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결과 데이터나 앱에서의 입력 데이터 주고 받기 위한 기능 개발이라던가&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DSP보단 iOS 앱 개발을 위해 더 집중했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DSP 개발도 보통 그렇다곤 하는데 이번에 만들면서 완전 경험했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;마무리 소감&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로의 DSP 공부 및 개발을 위한 중요한 경험이 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 앱에 더 집중한다고 하긴 했지만 레이어를 나누고 구조를 생각하면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 플러그인 개발할때 어떻게 구조를 잡을지 힌트가 많이 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YIN 알고리즘을 공부하면서 소리 데이터를 다루고 원하는 값을 얻기 위한&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계산들을 학습하고 공부하면서 앞으로의 DSP 공부에 도움이 많이 될거라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 지금하고 있는 DSP 공부가 이전보다 많이 수월하고 편해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 마지막 iOS 개발이 될거 같고 그랬으면 좋겠다.&lt;/p&gt;</description>
      <category>JDTuner/개발기록</category>
      <author>JD-man</author>
      <guid isPermaLink="true">https://jd-man.tistory.com/82</guid>
      <comments>https://jd-man.tistory.com/82#entry82comment</comments>
      <pubDate>Mon, 4 May 2026 20:14:53 +0900</pubDate>
    </item>
    <item>
      <title>OctaFuzz 기획</title>
      <link>https://jd-man.tistory.com/81</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;기획 목적 및 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 쓰려고 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 주로 사용하는 팀헨슨 플러그인에는 Octaver, Fuzz가 없는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합주나 공연을 위해 저 두개가 필요한 실리카겔곡을 연습하려면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쩔수없이 멀티이펙터를 켜야됐고 생각보다 많이 번거로웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난 그냥 집에서 간편히 연습하려고 하는데 멀티를 켜기는 싫었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Octaver와 Fuzz가 필요하기 때문에 두 기능을 하나의 플러그인으로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Octaver&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터는 oct + 1, -1, mix를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POG 같은 소리가 나야하므로 플리포닉 트래킹으로 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해서 크로스페이딩 딜레이 라인 또는 타임 도메인 그래뉼라 피치 시프팅 기법을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Fuzz&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 하나의 파라미터를 가지는 퍼즈로 만들려고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 EQ나 볼륨은 이후 이펙터나 앰프에서 조절하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만들다가 이펙터 사용 전후로 볼륨차이가 너무 많이 나면 볼륨 파라미터는 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 순서 변경&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fuzz -&amp;gt; Octaver의 순서를 기본으로 사용하겠지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 실험을 하거나 다른 소리를 만들고 싶을 수 있게끔&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 두개의 체인 순서를 변경하는 기능을 넣으려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기술 스택&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUCE 및 C++을 이용하고 플러그인 형태로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IDE는 Xcode를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃은 OctaFuzz 레포지토리와 기존의 DSP 모듈 레포지토리를 나눠서 관리하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OctaFuzz의 프로젝트가 DSP 모듈 내의 Octaver, Fuzz 부분을 참조하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디자인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인이 문제긴한데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUCE 내의 UI를 사용해 그리기 보단&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대한 이미지 파일을 활용해 페달 모양으로 만들려고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업만 별도로 정리해야할 정도로 도전적인 경험이 될듯.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;AI 활용&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번엔 개발하면서 처음으로&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;시작부터 AI를 사용해 프로젝트를 만들 생각이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이론부터 공부하고 코드를 작성하는건 지금 시대에는 맞지 않은거 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이론 학습이 중요하므로 AI가 던져주는 이론과 코드를 학습해&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전과는 역으로 선개발후학습을 하려고 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(안그러면 1년걸림)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발 기간&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 나에게 최우선은 개발보단 연습이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 시간이 많아져서 시간에 쫓겨 개발하기 보단&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;천천히 이론들을 학습하면서 개발할 계획&lt;/p&gt;</description>
      <category>OctaFuzz/개발기록</category>
      <author>JD-man</author>
      <guid isPermaLink="true">https://jd-man.tistory.com/81</guid>
      <comments>https://jd-man.tistory.com/81#entry81comment</comments>
      <pubDate>Wed, 29 Apr 2026 16:46:38 +0900</pubDate>
    </item>
    <item>
      <title>튜닝 모드 변경</title>
      <link>https://jd-man.tistory.com/80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;환절기에 골골대느라 2주를 날렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽃가루 알레르기, 비염, 감기 난리가 해가 갈수록 심해진다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트의 마지막으로 추가할 기능인 튜닝 모드 변경 기능을 만들어봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대상 주파수의 외부 주입&lt;/h2&gt;
&lt;pre id=&quot;code_1776925581866&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TunerResult JDTuner::getGuitarTunerResult(float frequency)
{
  if (frequency &amp;lt; 40.0f || frequency &amp;gt; 500.0f)
    return {result};

  // 1. 기타 6줄의 표준 주파수 배열
  float guitarStrings[] = {82.41f, 110.00f, 146.83f, 196.00f, 246.94f, 329.63f};
  const char *guitarNoteNames[] = {&quot;6E&quot;, &quot;5A&quot;, &quot;4D&quot;, &quot;3G&quot;, &quot;2B&quot;, &quot;1E&quot;};
  
  // 이 주파수 배열을 이용한 주파수 검출...

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존은 위와 같이 함수 내에서 목표 주파수와 노트를 직접 입력해놓은 코드였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜닝 모드를 변경하기 위해서는 이런 하드 코딩보단 외부에서 받아서 사용할 필요가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프리셋 생성&lt;/h2&gt;
&lt;pre id=&quot;code_1776925839525&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  presetDictionary = {
    {&quot;GuitarStandardE&quot;, {
      {82.41f, &quot;6E&quot;}, {110.00f, &quot;5A&quot;}, {146.83f, &quot;4D&quot;},
      {196.00f, &quot;3G&quot;}, {246.94f, &quot;2B&quot;}, {329.63f, &quot;1E&quot;}
    }},
    {&quot;GuitarDropD&quot;, {
      {73.42f, &quot;6D&quot;}, {110.00f, &quot;5A&quot;}, {146.83f, &quot;4D&quot;},
      {196.00f, &quot;3G&quot;}, {246.94f, &quot;2B&quot;}, {329.63f, &quot;1E&quot;}
    }},
    
    // ...
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;presetDictionary 프로퍼티를 만들고 튜너 생성시 초기화 되도록 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것도 별도의 객체로 만들어 놓고 싶었지만 지금은 거기까진 필요없어서 일단 이렇게 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;튜닝 모드 변경 메서드 구현&lt;/h2&gt;
&lt;pre id=&quot;code_1776926039045&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// JDTuner

void JDTuner::setTuningMode(const std::string&amp;amp; modeName) {
  auto it = presetDictionary.find(modeName);
  
  if (it != presetDictionary.end()) {
    targetNotes = it-&amp;gt;second;
  } else {
    targetNotes = presetDictionary.at(&quot;GuitarStandardE&quot;);
  }
}

// JDTunerEngine

void JDTunerEngine::setTuningMode(const std::string &amp;amp;modeName) {
  jdTuner.setTuningMode(modeName);
}

// JDTunerWrapper

- (void)setTuningMode:(NSString *)modeName {
  if (engine) {
    std::string cppModeName = [modeName UTF8String];
    engine-&amp;gt;setTuningMode(cppModeName);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너부터 시작해 engine, wrapper에서 각각 튜닝 모드 변경 메서드를 만들어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰에서 wrapper의 메서드를 사용하도록 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;뷰 구현&lt;/h2&gt;
&lt;pre id=&quot;code_1776926303727&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// enum
enum TuningMode: String, CaseIterable {
  case guitarStandard = &quot;GuitarStandardE&quot;
  case guitarDropD = &quot;GuitarDropD&quot;
  case bassStandard = &quot;BassStadardE&quot;
  
  var displayName: String {
    switch self {
    case .guitarStandard: return &quot;Guitar Standard (E)&quot;
    case .guitarDropD: return &quot;Guitar Drop D&quot;
    case .bassStandard: return &quot;Bass Standard (E)&quot;
    }
  }
}

// State...

@State private var selectedMode: TuningMode = .guitarStandard


// View...
     HStack {
          Text(&quot;Tuning Mode&quot;)
            .font(.headline)
            .foregroundColor(.white.opacity(0.6))
          
          Spacer()
          
          Picker(&quot;Tuning Mode&quot;, selection: $selectedMode) {
            ForEach(TuningMode.allCases, id: \.self) { mode in
              Text(mode.displayName).tag(mode)
            }
          }
          .pickerStyle(.menu)
          .tint(.white)
          .lineLimit(1)
          .fixedSize(horizontal: true, vertical: false)
          // 값이 변경될 때마다 C++ Wrapper로 전달
          .onChange(of: selectedMode, initial: false, { oldValue, newValue in
            if oldValue != newValue {
              wrapper.setTuningMode(newValue.rawValue)
            }
          })
        }
        .padding(.horizontal, 24)
        .padding(.top, 20)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열거형을 정의하고 (지금보니까 오타가 있는데 나중에 수정해야겠다) 간단한 Picker를 만들어 튜닝 모드를 변경했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;1087&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbCVbS/dJMcajvcayO/pj0lEfkutCJz4Ex9H1KOAk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbCVbS/dJMcajvcayO/pj0lEfkutCJz4Ex9H1KOAk/img.gif&quot; data-alt=&quot;결과 영상&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbCVbS/dJMcajvcayO/pj0lEfkutCJz4Ex9H1KOAk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bbCVbS/dJMcajvcayO/pj0lEfkutCJz4Ex9H1KOAk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;349&quot; height=&quot;759&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;1087&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과 영상&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜닝 모드까지 변경하는 기능을 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 실기기 테스트와 시연 영상 만들고 이 프로젝트는 마무리 예정!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마무리 포스트에 소감을 작성하겠지만 c++보다 iOS쪽 작업이 더 많았다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼른 마무리하고 다음 프로젝트로 가야지!!!&lt;/p&gt;</description>
      <category>JDTuner/개발기록</category>
      <author>JD-man</author>
      <guid isPermaLink="true">https://jd-man.tistory.com/80</guid>
      <comments>https://jd-man.tistory.com/80#entry80comment</comments>
      <pubDate>Thu, 23 Apr 2026 15:46:59 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트 구조 변경 - 튜너를 외부로 분리</title>
      <link>https://jd-man.tistory.com/79</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;변경 전 구조&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK7pgh/dJMcaiQkX2x/KFAlouOCcyOQvDx24RCMQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK7pgh/dJMcaiQkX2x/KFAlouOCcyOQvDx24RCMQ0/img.png&quot; data-alt=&quot;변경 전 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK7pgh/dJMcaiQkX2x/KFAlouOCcyOQvDx24RCMQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK7pgh%2FdJMcaiQkX2x%2FKFAlouOCcyOQvDx24RCMQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;666&quot; height=&quot;364&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;변경 전 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지는 변경 전 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI를 제외한 나머지 레이어의 역할을 설명하면&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DSP Engine Layer&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주파수 검출을 위한 Yin 알고리즘을 실행시키고 Wrapper로 결과를 전달한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Wrapper Layer&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너로 부터 받은 결과 값을 WrapperResult로 변경해 UI로 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 cents값의 제한을 두게 하거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;smoothFrequency와 같은 &lt;b&gt;데이터에 관여하는 코드가 포함&lt;/b&gt;되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제점 1&amp;nbsp; - Wrapper에서의 불필요한 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Wrapper는 c++에서 받은 값을 Swift로 전달하는 역할만 가지고 있어야 한다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스 로직과 같은 데이터를 다루는 코드가 들어갈 경우 튜너 프로젝트와 Wrapper 둘다 수정해야하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;c++, Objective-C 간의 호환코드도 들어가 기능이 추가될수록 유지보수가 어려워 질거라고 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제점 2 - DSP 코드의 재사용성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 iOS 프로젝트만 만들고 있지만 안드로이드나 내가 만들 플러그인에도 해당 튜너 알고리즘을 사용할 경우도 생각하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너 코드까지 iOS 프로젝트에 포함되어 있는 경우 각 프로젝트마다 튜너 코드도 다시 작성해야하는 문제점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 현재 프로젝트에서도 튜너쪽 기능을 추가하려고 하면 DSP Engine 쪽을 건드리게 되는데 이런 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수한 튜너의 코드까지 건드리게 돼서 불리한점이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 방안&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나만의 DSP 코드 레포지토리를 따로 분리하고 각 프로젝트에서 이 DSP 레포지토리에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 기능의 C++ 코드를 의존하는 방향으로 변경해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수 DSP 코드를 건드리지 않으면서 각 프로젝트의 목적 및 기능에 맞게 개발하게 구조를 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약하면 DSP 부분만 외부로 빼내기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DSP 코드만 모아놓는 레포지토리를 별도로 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너 코드를 그 폴더에 이동 시켰으며 jucer에서 해당 코드를 참조하도록 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱과 튜너 프로젝트 모두 헤더 경로 설정이 필요했으며 개발환경 설정의 경험이 있어 어렵지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변경 후 구조&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baywUz/dJMcab4MA9p/ayC8nbfZ6CNKR5af5sPN9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baywUz/dJMcab4MA9p/ayC8nbfZ6CNKR5af5sPN9K/img.png&quot; data-alt=&quot;변경 후 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baywUz/dJMcab4MA9p/ayC8nbfZ6CNKR5af5sPN9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaywUz%2FdJMcab4MA9p%2FayC8nbfZ6CNKR5af5sPN9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;491&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;변경 후 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 내의 Engine Layer가 외부의 DSP 코드에 의존하게되며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트의 경우 외부 DSP 코드의 튜너만을 참조해 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게해서 순수 DSP 코드는 특정 프로젝트에서 개발하더라도 유지가 가능해졌고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 프로젝트에 재사용 가능하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이후 작업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Wrapper 코드에 있는 데이터 관여 코드를 Engine으로 넘기고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Wrapper는 최대한 C++쪽의 결과를 앱으로 넘기는 기능만 남겨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 세밀한 역할 분리 작업을 할 예정이다.&lt;/p&gt;</description>
      <category>JDTuner/개발기록</category>
      <author>JD-man</author>
      <guid isPermaLink="true">https://jd-man.tistory.com/79</guid>
      <comments>https://jd-man.tistory.com/79#entry79comment</comments>
      <pubDate>Fri, 3 Apr 2026 20:19:42 +0900</pubDate>
    </item>
    <item>
      <title>UI 수정</title>
      <link>https://jd-man.tistory.com/78</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선 이전&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;1082&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d0hkGI/dJMcaf6ULDh/0owJPSx2q5aT0oBjdiIlS0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0hkGI/dJMcaf6ULDh/0owJPSx2q5aT0oBjdiIlS0/img.gif&quot; data-alt=&quot;개선 전 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0hkGI/dJMcaf6ULDh/0owJPSx2q5aT0oBjdiIlS0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/d0hkGI/dJMcaf6ULDh/0owJPSx2q5aT0oBjdiIlS0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;321&quot; height=&quot;695&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;1082&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개선 전 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선 전 디자인은 &lt;b&gt;숫자 밖에 모르는 공돌이 인간&lt;/b&gt;이 만든 테스트용 화면 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;출시까지 고민하고 있는 만큼 조금 더 상용 앱 같은 느낌으로 수정하고 싶었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;후보군&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-14 오후 6.56.12.png&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;1108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HtpM6/dJMcabDsSKJ/5udvNcHCOgD0c6TZbBUe6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HtpM6/dJMcabDsSKJ/5udvNcHCOgD0c6TZbBUe6K/img.png&quot; data-alt=&quot;제미나이의 3가지 개선안&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HtpM6/dJMcabDsSKJ/5udvNcHCOgD0c6TZbBUe6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHtpM6%2FdJMcabDsSKJ%2F5udvNcHCOgD0c6TZbBUe6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1834&quot; height=&quot;1108&quot; data-filename=&quot;스크린샷 2026-03-14 오후 6.56.12.png&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;1108&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;제미나이의 3가지 개선안&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사실 위에서말한 숫자밖에 모르는 공돌이 인간이 나&lt;/b&gt;임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 제미나이에게 디자인 개선안을 물어봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1번&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미니멀리즘을 추구하는 나는 정말 마음에 드는 디자인이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;군더더기 없이 깔끔하고 이후에 기능 추가하기에도 좋아보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2번&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6현 기타의 헤드가 전부 저런 모양이 아니며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 7~8현 기타의 튜닝도 만들어보고 싶은 생각이 들어 패스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3번&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래프와 슬라이드 전부 좋았지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 리소스가 너무 많이 들어서 배보다 배꼽이 더 커보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 빨리 튜닝하고 다른거 해야할건데 이거 때문에 핸드폰이 뜨거워지면 안될거 같았다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 패스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최종 선택 - 1번 디자인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 1번이 선택됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이 디자인 그대로 한건 아니고 최대한 가깝게 구현하려고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 및 상태값은 그대로 사용했으므로 UI만 수정하면 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 스유 UI 코드라서 딱히 정리할게 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Simulator Screen Recording - iPhone 17 Pro - 2026-03-15 at 17.15.53.gif&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ovTUO/dJMcabDsSOq/UdkrQeo393uPoOzDSLOr8k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ovTUO/dJMcabDsSOq/UdkrQeo393uPoOzDSLOr8k/img.gif&quot; data-alt=&quot;개선 이후&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ovTUO/dJMcabDsSOq/UdkrQeo393uPoOzDSLOr8k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/ovTUO/dJMcabDsSOq/UdkrQeo393uPoOzDSLOr8k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;295&quot; height=&quot;640&quot; data-filename=&quot;Simulator Screen Recording - iPhone 17 Pro - 2026-03-15 at 17.15.53.gif&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개선 이후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금보면 빨간색과 파란색으로 굳이 나눠야 싶긴 하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+, - 상태를 나타내기에 직관적으로 좋아보여서 그냥 놔두려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에 있는 슬라이더는 디버그 모드에서만 생기는 슬라이더다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시뮬레이터에서 UI만 확인하고 싶은데 폰에 빌드하기 싫어서 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미니멀한게 내 스타일이고 이후 내가 붙이고 싶은 기능이 있을때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 디자인 수정없이도 가능해보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 기능 테스트를 몇번 더 하고 성능을 개선하고 출시를 고려해봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인 만들때도 느낀거지만 여기 DSP쪽도 결국 UI 노동인거 같다...&lt;/p&gt;</description>
      <category>JDTuner/개발기록</category>
      <author>JD-man</author>
      <guid isPermaLink="true">https://jd-man.tistory.com/78</guid>
      <comments>https://jd-man.tistory.com/78#entry78comment</comments>
      <pubDate>Sun, 15 Mar 2026 17:30:26 +0900</pubDate>
    </item>
    <item>
      <title>정확도 개선 2 - 1차 IIR 필터</title>
      <link>https://jd-man.tistory.com/77</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크를 사용하는 앱이므로 어지간하면 조용한곳에서 사용하겠지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로 들어가는 소리는 단순히 기타 줄의 소리만 들어가는게 아니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 배음&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기타 줄에서 나오는 파형은 여러개의 파형의 합쳐져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6번 줄(82.4Hz)을 예로 들면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정배수인 &lt;b data-index-in-node=&quot;77&quot; data-path-to-node=&quot;4&quot;&gt;164.8Hz(2배음), 247.2Hz(3배음), 329.6Hz(4배음)&lt;/b&gt; 등의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고음역대 소리가 동시에, 치는 순간에는 원래 음보다 더 강하게 튀어나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 피킹음&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피킹시 피크(Pick)가 줄을 긁는 고음역대의 쇳소리도 들어갈 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 주변 소음&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TV를 틀어 놓거나 하는 주변 소음도 어느정도 있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결방식 - 1차 IIR 필터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 여러소리가 같이 들어가는 상황에서 최대한 기타 줄의 음역대의 영역으로 가져오기 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필터가 필요하고 이럴때 간단한 로우패스 필터를 사용하면 도움이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이론&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hxo1m/dJMcaaLgPQA/BaHKGSq5TSS1nRBNH4tGc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hxo1m/dJMcaaLgPQA/BaHKGSq5TSS1nRBNH4tGc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hxo1m/dJMcaaLgPQA/BaHKGSq5TSS1nRBNH4tGc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHxo1m%2FdJMcaaLgPQA%2FBaHKGSq5TSS1nRBNH4tGc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;634&quot; height=&quot;112&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재의 결과값을 현재의 입력값 x[n]과 바로 직전의 결과값 y[n-1]을 가중치 알파를 통해 더한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcUhC7/dJMcab4tdFz/OA42rvJMiznRs2YbU1pdh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcUhC7/dJMcab4tdFz/OA42rvJMiznRs2YbU1pdh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcUhC7/dJMcab4tdFz/OA42rvJMiznRs2YbU1pdh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcUhC7%2FdJMcab4tdFz%2FOA42rvJMiznRs2YbU1pdh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;544&quot; height=&quot;160&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 샘플을 돌면서 계산하게 되므로 결국 y[n]은 위와 같은 식으로 나오고 이전 값이 지수적으로 영향을 미쳐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지수 이동 평균이라고도 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvJ1YK/dJMcaiP1r4C/cA6Hk9kHWVeIV5fKhIltdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvJ1YK/dJMcaiP1r4C/cA6Hk9kHWVeIV5fKhIltdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvJ1YK/dJMcaiP1r4C/cA6Hk9kHWVeIV5fKhIltdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvJ1YK%2FdJMcaiP1r4C%2FcA6Hk9kHWVeIV5fKhIltdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1148&quot; height=&quot;430&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알파 값을 0.2라고 가정하면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,0,0&quot;&gt;새로운 소리의 반영 비율이 20%밖에 안돼서 &lt;/b&gt;갑자기 날카로운 고주파 소리(빠르게 진동하는 파형)가 확 들어와도, 80%의 과거 데이터가 끌어내린다.&lt;/li&gt;
&lt;li&gt;결과적으로&lt;b&gt; 빠르게 변하는 고주파(배음, 잡음)는 억제되고, 천천히 변하는 저주파(기타의 기본음)만 살아&lt;/b&gt;남는다.&lt;/li&gt;
&lt;li&gt;기존 음의 영향력은 샘플이 진행될수록 점점 약해져 완만하게 필터링이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드 적용&lt;/h2&gt;
&lt;pre id=&quot;code_1773229603582&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// JDTunerEngine.h
  private:
  
  // ...
  
  // 로우패스 필터용 변수
  float prevLpfOut = 0.0f; // 필터의 이전 출력값을 기억
  float lpfAlpha = 0.2f;   // 필터 강도 (0.0~1.0, 작을수록 고음을 더 강하게 깎음)
  
// JDTunerEngine.cpp

// ...
void JDTunerEngine::collectData(const float* datas, int numSamples) {
  for (int i = 0; i &amp;lt; numSamples; ++i) {
    // 로우패스 필터 적용
    float filteredSample = lpfAlpha * datas[i] + (1.0f - lpfAlpha) * prevLpfOut;
    prevLpfOut = filteredSample;
    collector.push_back(filteredSample);
  }
}
// ...&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-path-to-node=&quot;20&quot;&gt;
&lt;li&gt;헤더쪽에 private으로 필터 관련 값을 정의한다.&lt;/li&gt;
&lt;li&gt;이전값을 저장하는 프로퍼티를 만들어서 줄바꿈시 기존 줄값을 저장하는거에 대한 걱정이 있었으나&lt;br /&gt;위의 예시를 봤을때 10칸의 데이터만해도 2%만 영향을 주므로&lt;br /&gt;4000개 이상의 버퍼 단위로 다루는 현재 상황에선 줄바꿈시 기존 줄의 값이 남아있어도 &lt;br /&gt;큰 영향이 없고 심지어 아예 영향이 없는 수준이어서 연속성을 위해 프로퍼티를 사용하는 방법으로 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;불필요한 고음역대와 잡음을 최대한 필터링하는 방식을 적용해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확도를 더 개선했고 DSP에서 자주 사용되는 1차 필터를 학습했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지수 이동 평균 방식은 합연산을 이용해 메모리 관리에 용이한 알고리즘으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인 만들때도 사용해본적이 있었던 만큼 다시 학습하고 사용해봐서 좋았다.&lt;/p&gt;</description>
      <category>JDTuner/개발기록</category>
      <author>JD-man</author>
      <guid isPermaLink="true">https://jd-man.tistory.com/77</guid>
      <comments>https://jd-man.tistory.com/77#entry77comment</comments>
      <pubDate>Wed, 11 Mar 2026 20:51:59 +0900</pubDate>
    </item>
    <item>
      <title>정확도 개선 1 - Parabolic Interpolation</title>
      <link>https://jd-man.tistory.com/76</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재의 pitchTau은 int 타입으로 정수값으로만 계산된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 V 형태로 정수 사이값이 고려되지 않은 선형적으로 계산된 값이고 자연스럽지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 tau값 기준으로 -1, +1을 포함한 3개의 값을 지나는 포물선을 가정하고 최소값 위치를 구하는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Parabolic Interpolation이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;370&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi71hV/dJMcaaR3LjD/kxFWdGKNQhK2GICN87a3Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi71hV/dJMcaaR3LjD/kxFWdGKNQhK2GICN87a3Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi71hV/dJMcaaR3LjD/kxFWdGKNQhK2GICN87a3Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi71hV%2FdJMcaaR3LjD%2FkxFWdGKNQhK2GICN87a3Qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;370&quot; height=&quot;112&quot; data-origin-width=&quot;370&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 포물선이 있다고 가정하고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk83bI/dJMcabQVOlz/cP73F4hb3YugKZPwxvA5J0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk83bI/dJMcabQVOlz/cP73F4hb3YugKZPwxvA5J0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk83bI/dJMcabQVOlz/cP73F4hb3YugKZPwxvA5J0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk83bI%2FdJMcabQVOlz%2FcP73F4hb3YugKZPwxvA5J0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;218&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재의 tau값을 0이라고 가정하고 양 옆 점이 위와 같다고 하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvDKYR/dJMcabpTRCV/HgkdKtvCkkzWfzxoay0Bsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvDKYR/dJMcabpTRCV/HgkdKtvCkkzWfzxoay0Bsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvDKYR/dJMcabpTRCV/HgkdKtvCkkzWfzxoay0Bsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvDKYR%2FdJMcabpTRCV%2FHgkdKtvCkkzWfzxoay0Bsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;355&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 식을 이용해 연립방정식을 계산하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0LMYR/dJMcai3xO1V/WEMkcqQIpYXy4kD2kkNh1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0LMYR/dJMcai3xO1V/WEMkcqQIpYXy4kD2kkNh1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0LMYR/dJMcai3xO1V/WEMkcqQIpYXy4kD2kkNh1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0LMYR%2FdJMcai3xO1V%2FWEMkcqQIpYXy4kD2kkNh1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;488&quot; height=&quot;158&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;c는 베타이기 때문에 위의 결과가 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OAndk/dJMcaflvGCS/Vl8i0oEPSBKP8b3NQMxJsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OAndk/dJMcaflvGCS/Vl8i0oEPSBKP8b3NQMxJsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OAndk/dJMcaflvGCS/Vl8i0oEPSBKP8b3NQMxJsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOAndk%2FdJMcaflvGCS%2FVl8i0oEPSBKP8b3NQMxJsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;136&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;264&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0QVgY/dJMcacvzK5o/oNPd3pGERXfy5x5VqIbr31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0QVgY/dJMcacvzK5o/oNPd3pGERXfy5x5VqIbr31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0QVgY/dJMcacvzK5o/oNPd3pGERXfy5x5VqIbr31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0QVgY%2FdJMcacvzK5o%2FoNPd3pGERXfy5x5VqIbr31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;264&quot; height=&quot;138&quot; data-origin-width=&quot;264&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식 A와 B를 더하면 a, 빼면 b를 구할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5qQWk/dJMcacI6DYx/mmPCK6Gx4tViIJK9JixRGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5qQWk/dJMcacI6DYx/mmPCK6Gx4tViIJK9JixRGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5qQWk/dJMcacI6DYx/mmPCK6Gx4tViIJK9JixRGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5qQWk%2FdJMcacI6DYx%2FmmPCK6Gx4tViIJK9JixRGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;286&quot; height=&quot;48&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;334&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czTwZy/dJMcahwSe1k/KlwJnmmCxUGKbsdMtdupB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czTwZy/dJMcahwSe1k/KlwJnmmCxUGKbsdMtdupB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czTwZy/dJMcahwSe1k/KlwJnmmCxUGKbsdMtdupB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczTwZy%2FdJMcahwSe1k%2FKlwJnmmCxUGKbsdMtdupB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;334&quot; height=&quot;128&quot; data-origin-width=&quot;334&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 처음 가정한 포물선 방정식을 이용하고 이를 미분해서 최소값이 나오는 x 값을 구하면 위와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6l0hH/dJMcacWBk3X/bIQ66VnhT217JW3CbOlDWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6l0hH/dJMcacWBk3X/bIQ66VnhT217JW3CbOlDWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6l0hH/dJMcacWBk3X/bIQ66VnhT217JW3CbOlDWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6l0hH%2FdJMcacWBk3X%2FbIQ66VnhT217JW3CbOlDWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;470&quot; height=&quot;152&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 구한 a와 b 값을 대입하면 x 값을 구할 수 있고 tau를 0이라고 가정했으므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 값을 tau에 더해주면 선형적으로 구해진 tau 정수값에서 소수점까지 보정된 값을 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;적용된 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1773226675192&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        // 3. Parabolic Interpolation
        if (tau &amp;gt; 0 &amp;amp;&amp;amp; tau &amp;lt; (collectorSize / 2) - 1) {
          float alpha = normalizedDifference[tau - 1];
          float beta  = normalizedDifference[tau];
          float gamma = normalizedDifference[tau + 1];
          
          float denominator = alpha - 2.0f * beta + gamma;
          if (denominator != 0.0f) {
            pitchTau = tau + 0.5f * (alpha - gamma) / denominator;
            break;
          }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 pitchTau 값을 int에서 float으로 타입을 변경하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tau에 보정값을 결과값으로 사용하도록 코드를 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수값만 사용했을때보다 그 사이의 소수점 사이값도 모두 사용하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너의 정확도도 향상되고 변화된 음정을 cents 단위로 더 잘 파악할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DSP 쪽에서 다루는 불연속적인 샘플들을 연속적인 선으로서 파악하게 하는 중요한 보간법이라고 하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 잘 공부해서 적용했다!&lt;/p&gt;</description>
      <category>JDTuner/개발기록</category>
      <author>JD-man</author>
      <guid isPermaLink="true">https://jd-man.tistory.com/76</guid>
      <comments>https://jd-man.tistory.com/76#entry76comment</comments>
      <pubDate>Wed, 11 Mar 2026 20:02:55 +0900</pubDate>
    </item>
    <item>
      <title>구조 개선 2 - 비동기스트림</title>
      <link>https://jd-man.tistory.com/75</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;현재 구조 및 문제점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 구조는 튜너쪽에서 계속 오디오 입력을 받아 결과를 계산하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰쪽에서 Timer를 통해 0.1초마다 Wrapper를 통해 튜너의 결과를 사용하는 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1773142720765&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;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
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너의 기능을 본격적으로 사용하고 있든 말든&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;뷰쪽에서는 Timer로 계속 호출해 낭비가 생긴다고 판단해&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;튜너에서 값이 실제로 업데이트 될때마다 뷰를 업데이트 하는 방식으로 구조를 변경하려고 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;구조 후보&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 델리게이트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 앱에서 만들때 객체간 데이터 교환시 자주 사용되는 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 뷰 하나밖에 없는 프로젝트에 비해&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프로토콜 및 인터페이스 선언 등&lt;span&gt; 불필요하게 볼륨이 커질거 같아 패스&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 콜백&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;델리게이트를 사용하지 않는다면 클로저를 자주 사용하니 떠올릴 수 있는 방식이지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 지옥에서 탈출하고 싶고 약한 참조 등 메모리 관리가 까다로울거 같아서 패스&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 비동기 스트림&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI도 사용하고 있고 modern concurrency를 사용하는게 성능상으로도 좋고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리도 안정적일거 같아서 채택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작업 과정&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 튜너쪽 c++ 콜백 생성&lt;/h4&gt;
&lt;pre id=&quot;code_1773143082882&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::function&amp;lt;void(TunerResult)&amp;gt; onResultReady;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헤더쪽에 Wrapper로 데이터를 넘긴 콜백 함수를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태는 std::function&amp;lt;반환타입(파라미터타입)&amp;gt; 변수명 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773143181512&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    this-&amp;gt;result = result; 
    collector.clear(); 
     
    // 데이터 처리가 완료되면 등록된 콜백 실행 
    if (onResultReady) { 
      onResultReady(result); 
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너의 구현부쪽에는 맨 마지막에 해당 콜백 함수를 실행시키는 코드를 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Wrapper쪽 콜백 생성&lt;/h4&gt;
&lt;pre id=&quot;code_1773143240228&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@property (nonatomic, copy) void (^onResultUpdate)(WrapperResult *result);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헤더쪽에 Swift쪽으로 값을 넘겨줄 콜백을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;void (^이름)(매개변수) 형태는 Objective-C의 고유 문법이며, Swift에서는 클로저로 인식한다.&lt;/p&gt;
&lt;pre id=&quot;code_1773143295415&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    
    // init....
    __weak typeof(self) weakSelf = self;
    engine-&amp;gt;onResultReady = [weakSelf](TunerResult tunerResult) {
      [weakSelf getTunerResult:tunerResult];
    };
  }
  return self;
}

- (void)getTunerResult: (TunerResult) tunerResult {
  WrapperResult *result = [WrapperResult new];
  result.frequency = tunerResult.frequency;
  
  float clampedCents = fmaxf(-50.0f, fminf(50.0f, tunerResult.cents));
  result.cents = clampedCents;
  result.noteName = [NSString stringWithUTF8String:tunerResult.noteName.c_str()];
  result.isMatched = std::abs(clampedCents) &amp;lt;= _centsLimit;
  
  // 튜너로 값을 받아 뷰로 넘긴다
  if (_onResultUpdate) {
    dispatch_async(dispatch_get_main_queue(), ^{
      self-&amp;gt;_onResultUpdate(result);
    });
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현부쪽에서는 튜너 결과값을 정리해 콜백을 실행시키는 코드를 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. Wrapper의 AsyncStream화&lt;/h4&gt;
&lt;pre id=&quot;code_1773143392498&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension JDTunerWrapper {
  var resultsStream: AsyncStream&amp;lt;WrapperResult&amp;gt; {
    AsyncStream { continuation in
      self.onResultUpdate = { result in
        guard let result else { return }
        continuation.yield(result)
      }
      continuation.onTermination = { @Sendable _ in
        self.onResultUpdate = nil
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 파일을 만들고 Wrapper를 extension해 AsyncStream을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 WrapperResult값을 받아서 continuation으로 yield하는 코드다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰쪽에서 이걸 받아서 상태값을 업데이트 해주려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.&amp;nbsp; 뷰에서 task 사용&lt;/h4&gt;
&lt;pre id=&quot;code_1773143480330&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    .task {
      // wrapper의 asyncstream 사용
      for await newResult in wrapper.resultsStream {
        self.result = newResult
      }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onReceive 코드를 삭제하고 task 코드로 수정해 비동기 스트림을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과 및 추가 개선 방향&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너의 주파수 및 노트 검출 성능에는 영향이 없지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 구조를 개선해 Timer의 부정확한 타이밍 또는 불필요한 호출을 제거하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진정한 결과값의 변화를 사용하고 표시하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 확실한 타이밍에 뷰를 업데이트 하기 위해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 일정 입력 볼륨값일때만 튜너 작동하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 기존 결과값과 다른 경우에만 표시하게 하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같은 작업도 필요해보인다. 이건 다음 포스트에...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JDTuner/개발기록</category>
      <author>JD-man</author>
      <guid isPermaLink="true">https://jd-man.tistory.com/75</guid>
      <comments>https://jd-man.tistory.com/75#entry75comment</comments>
      <pubDate>Tue, 10 Mar 2026 20:55:39 +0900</pubDate>
    </item>
    <item>
      <title>구조 개선 1 - 데이터 관리 + 테스트 영상</title>
      <link>https://jd-man.tistory.com/74</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제점&lt;/h2&gt;
&lt;pre id=&quot;code_1773055924383&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- (float)getFrequency {
    return engine-&amp;gt;getResult().frequency;
}

- (NSString *)getNoteName {
    TunerResult res = engine-&amp;gt;getResult();
    return [NSString stringWithUTF8String:res.noteName.c_str()];
}

- (float)getCents {
    return engine-&amp;gt;getResult().cents;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Wrappper쪽 코드를 보면 튜너 결과값의 각 프로퍼티 당 메서드 하나를 사용해 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너쪽 c++ TunerResult 구조체의 변동에 따라 이쪽 코드를 유지보수하기 까다로워보여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift쪽에서도 데이터들을 하나로 묶어서 관리할 방법을 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결방식&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 클래스 정의&lt;/h4&gt;
&lt;pre id=&quot;code_1773056142341&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#ifndef WrapperResult_h
#define WrapperResult_h

@interface WrapperResult : NSObject
@property (nonatomic, assign) float frequency;
@property (nonatomic, assign) float cents;
@property (nonatomic, copy) NSString *noteName;
@property (nonatomic, assign) bool isMatched;
@end

#endif /* WrapperResult_h */&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WrapperResult라는 새로운 클래스를 정의했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 클래스냐?? 라고 하면 NSString * 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미개한 objc는 구조체 안에 참조 타입이 있으면 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 처음에 구조체로 만들었다가 각종 에러를 맞고 클래스로 변경했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Wrapper 헤더 및 구현 수정&lt;/p&gt;
&lt;pre id=&quot;code_1773056053587&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@property (nonatomic, assign) float centsLimit;

- (WrapperResult *)getTunerResult;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 헤더쪽 코드는 간단하게 하나의 프로퍼티와 메서드만 남도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;centsLimit은 isMatched 계산을 위한 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private이므로 구현코드에서는 앞에 _를 붙여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773056241592&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- (WrapperResult *)getTunerResult {
  auto engineResult = engine-&amp;gt;getResult();
  auto cents = fmaxf(-50.0f, fminf(50.0f, engineResult.cents));
  
  // new 사용해서 초기화하는 코드를 사용해야함
  WrapperResult *result = [WrapperResult new];
  
  result.frequency = engineResult.frequency;
  result.cents = engineResult.cents;
  result.noteName = [NSString stringWithUTF8String:engineResult.noteName.c_str()];
  result.isMatched = std::abs(cents) &amp;lt;= _centsLimit;
  return result;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 위의 3개의 코드가 하나의 메서드로 합쳐지고 객체의 프로퍼티 관리로 변경됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰에서는 이 메서드 하나만 호출하면 되므로 유지보수가 편해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 WrapperResult * 객체 생성시 [WrapperResult new]를 꼭 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 뷰 코드 수정&lt;/h4&gt;
&lt;pre id=&quot;code_1773056381946&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      let currentCents = max(-50, min(50, wrapper.getCents()))
      self.cents = currentCents
      self.noteName = wrapper.getNoteName()
      self.frequency = wrapper.getFrequency()
      self.isMatched = abs(currentCents) &amp;lt;= centsLimit

///////////////////////////////////////////////////////////////////////////
위에서 아래로 코드 변경

     guard let wrapperResult = wrapper.getTunerResult() else { return }
      result = wrapperResult&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onReceive쪽 코드가 위에서 아래로 간단하게 변경됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너 결과 쪽에서 프로퍼티가 하나 추가됐다면 Wrapper쪽에서 메서드 하나가 추가되고 뷰 쪽에서 여기 코드도 수정되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 지금 구조로 변경후 Wrapper 쪽에서 WrapperResult 객체 생성 메서드만 수정하면 되도록 유지보수가 용이해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트러블슈팅&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;objc에서 사용하거나 반환하는 포인터들은 Swift에서 기본적으로 Optional이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 당시엔 빌드가 정상이지만 런타임시 객체를 못찾아서 크래시가 나온다.&lt;/p&gt;
&lt;pre id=&quot;code_1773056564135&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Text(result.noteName ?? &quot;---&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰 코드 쪽에서 이런 코드가 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WrapperResult의 noteName이 NSString * 으로 포인터다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 값이 Swift에선 기본적으로 옵셔널이라 빌드 후 첫 뷰 렌더링때 값을 못찾아서 크래시가 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onReceive에서 반환되는 값도 WrapperResult * 로 포인터라서 옵셔널 바인딩을 해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과 및 다음 스텝&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상 빌드되고 튜너 기능도 정상 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 스텝은 뷰쪽에서 Timer를 사용해 튜너 결과값을 가져오는 구조가 아닌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너에서 결과값이 변경될때 그 값을 사용하는 반응형으로 수정하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰쪽에서 Timer를 사용해 원하지 않은 타이밍에 계속 튜너 값을 사용하기보단&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 값이 변경될때 사용하는게 맞는 구조라고 생각해서 진행하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식은 여러가지가 있겠지만 최대한 최신 문법을 사용하는 방향으로 진행하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 영상&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 가지고 있던 튜너와 같이 테스트를 진행해봤는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 감지하고 현재 음이 표시됐다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너는 진동을 받는 방식이고 앱은 소리를 받는 방식이라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;약간의 오차가 있지만&lt;span&gt; 원하는 기능이 제대로 실행되는걸 확인했다!!!&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;1082&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NUnaz/dJMb99Mjh1T/Bjxi94kYgvkb6qRddD6C5k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NUnaz/dJMb99Mjh1T/Bjxi94kYgvkb6qRddD6C5k/img.gif&quot; data-alt=&quot;테스트 결과!!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NUnaz/dJMb99Mjh1T/Bjxi94kYgvkb6qRddD6C5k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/NUnaz/dJMb99Mjh1T/Bjxi94kYgvkb6qRddD6C5k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;376&quot; height=&quot;814&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;1082&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테스트 결과!!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JDTuner/개발기록</category>
      <author>JD-man</author>
      <guid isPermaLink="true">https://jd-man.tistory.com/74</guid>
      <comments>https://jd-man.tistory.com/74#entry74comment</comments>
      <pubDate>Mon, 9 Mar 2026 20:54:20 +0900</pubDate>
    </item>
    <item>
      <title>튜너 UI 구현</title>
      <link>https://jd-man.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;디자인은 AI에게 맡겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 만들어 달라고 했는데 내 스타일대로 심플하게 잘 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;1082&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFVBgE/dJMcabDnK9S/XBMlndFKoqud3xtQ85yQyk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFVBgE/dJMcabDnK9S/XBMlndFKoqud3xtQ85yQyk/img.gif&quot; data-alt=&quot;튜너 뷰 UI&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFVBgE/dJMcabDnK9S/XBMlndFKoqud3xtQ85yQyk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cFVBgE/dJMcabDnK9S/XBMlndFKoqud3xtQ85yQyk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;289&quot; height=&quot;625&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;1082&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;튜너 뷰 UI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1772964971525&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;            // 움직이는 바늘
            VStack(spacing: 0) {
              Triangle()
                .fill(isMatched ? Color.green : Color.blue)
                .frame(width: 12, height: 12)
              Rectangle()
                .fill(isMatched ? Color.green : Color.blue)
                .frame(width: 2, height: 20)
            }
            .offset(x: CGFloat(cents) * 3) // 1센트당 3포인트 이동
            .animation(.interactiveSpring(response: 0.3, dampingFraction: 0.6), value: cents)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cents 값이 필요했던 이유다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;offset을 사용해서 cents값에 따라 튜너 바늘이 움직이게 해놨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BAD ACCESS 에러&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;BAD_EXCESS.png&quot; data-origin-width=&quot;2226&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X0aXO/dJMcabQTweW/39J1fsVOUSWj4UkDAdN8Ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X0aXO/dJMcabQTweW/39J1fsVOUSWj4UkDAdN8Ek/img.png&quot; data-alt=&quot;간헐적으로 발생하는 BAD&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X0aXO/dJMcabQTweW/39J1fsVOUSWj4UkDAdN8Ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX0aXO%2FdJMcabQTweW%2F39J1fsVOUSWj4UkDAdN8Ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2226&quot; height=&quot;236&quot; data-filename=&quot;BAD_EXCESS.png&quot; data-origin-width=&quot;2226&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;간헐적으로 발생하는 BAD&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BAD EXCESS인줄 알았는데 BAD ACCESS였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근이 불가능하다는 에러인데 왜 EXCESS라고 생각했지??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;암튼 가끔 이 에러가 발생하면서 크래시가 나왔는데 원인은 튜너쪽 TunerResult의 noteName이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;char* 여서 그랬다. Objective-C에서 c++의 주소값을 가져오려다 실패한것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 값복사를 사용하는 std:string으로 변경했다.&lt;/p&gt;
&lt;pre id=&quot;code_1772965212895&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct TunerResult {
  float frequency;
  std::string noteName;
  float cents;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;juce_Audio_ios Assertion&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-08 오후 6.57.56.png&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3erie/dJMcaaLevKS/1o9KLcqACKPukCIwe3Hhxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3erie/dJMcaaLevKS/1o9KLcqACKPukCIwe3Hhxk/img.png&quot; data-alt=&quot;또 assert가 발생&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3erie/dJMcaaLevKS/1o9KLcqACKPukCIwe3Hhxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3erie%2FdJMcaaLevKS%2F1o9KLcqACKPukCIwe3Hhxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;212&quot; data-filename=&quot;스크린샷 2026-03-08 오후 6.57.56.png&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;또 assert가 발생&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜너 뷰를 만들면서 마이크 권한 코드를 빼서 나왔던 assert 였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;info.plist에는 마이크 관련 내용이 있는데 코드에는 없으니 나오는거였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에서의 문제 같은데 이걸 JUCE에서 assert 처리 한게 신기했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 작업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 영상을 보면 튜너가 정확한건지 의심이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0.0값도 너무 자주 나오고 값이 너무 튀는 느낌이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 작업은 튜너 성능을 테스트하면서 안정적으로 개선하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어려운 작업이 될거 같은데 이 과정에서 배우는거도 많을거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI가 나온걸 보니 거의 다온거 같고 얼른 끝내고 싶다.&lt;/p&gt;</description>
      <category>JDTuner/개발기록</category>
      <author>JD-man</author>
      <guid isPermaLink="true">https://jd-man.tistory.com/73</guid>
      <comments>https://jd-man.tistory.com/73#entry73comment</comments>
      <pubDate>Sun, 8 Mar 2026 19:26:34 +0900</pubDate>
    </item>
  </channel>
</rss>