첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

■ 마이크 입력 스펙트로그램을 표시하는 방법을 보여준다.

TestSolution.zip
0.04MB

▶ ArgoColorMap.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 아르고 색상 맵
    /// </summary>
    public class ArgoColorMap : IColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// RGB 배열
        /// </summary>
        private readonly int[] rgbArray =
        {
            00000000, 00000004, 00000264, 00000267, 00000527, 00000530, 00000789, 00066328,
            00066588, 00066591, 00066849, 00132388, 00132647, 00132650, 00132908, 00198447,
            00198706, 00198708, 00264503, 00264505, 00330299, 00330557, 00330560, 00396354,
            00396612, 00462150, 00462408, 00527946, 00528204, 00593998, 00594000, 00659794,
            00660052, 00725590, 00791383, 00791641, 00857435, 00857437, 00923230, 00989024,
            00989026, 01054819, 01120613, 01120870, 01186408, 01252201, 01252459, 01318252,
            01384046, 01384047, 01449841, 01515634, 01515892, 01581429, 01647222, 01713016,
            01713273, 01779066, 01844860, 01910397, 01976190, 01976447, 02042241, 02108034,
            02173827, 02239364, 02239621, 02305415, 02371208, 02437001, 02502794, 02568587,
            02568844, 02634381, 02700174, 02765968, 02831761, 02897554, 02963347, 03029140,
            03029397, 03095190, 03160983, 03226520, 03292313, 03358106, 03423899, 03489692,
            03555485, 03621278, 03687071, 03752864, 03818656, 03884449, 03950242, 04016035,
            04081828, 04147621, 04147878, 04213671, 04279464, 04345256, 04411049, 04476842,
            04542635, 04608428, 04739757, 04805550, 04871342, 04937135, 05002928, 05068721,
            05134514, 05200306, 05266099, 05331892, 05397685, 05463477, 05529270, 05595063,
            05660856, 05726648, 05792441, 05858234, 05924027, 05989819, 06121148, 06186941,
            06252734, 06318526, 06384319, 06450112, 06515904, 06581953, 06647746, 06779074,
            06844867, 06910660, 06976452, 07042245, 07108038, 07173830, 07239623, 07370952,
            07436744, 07502793, 07568586, 07634378, 07700171, 07765964, 07897292, 07963085,
            08028877, 08094670, 08160719, 08226511, 08357840, 08423632, 08489425, 08555218,
            08621010, 08752339, 08818387, 08884180, 08949973, 09015765, 09147094, 09212886,
            09278679, 09344727, 09410520, 09541849, 09607641, 09673434, 09739226, 09870555,
            09936603, 10002396, 10068188, 10133981, 10265309, 10331102, 10397150, 10462943,
            10594272, 10660064, 10725857, 10791905, 10923234, 10989026, 11054819, 11120611,
            11251940, 11317988, 11383781, 11515109, 11580902, 11646694, 11712743, 11844071,
            11909864, 11975656, 12106985, 12173033, 12238826, 12304618, 12435947, 12501995,
            12567787, 12699116, 12764908, 12830701, 12962285, 13028078, 13093870, 13159663,
            13291247, 13357040, 13422832, 13554161, 13619953, 13686001, 13817330, 13883122,
            13948915, 14080499, 14146292, 14212084, 14343412, 14409461, 14475253, 14606582,
            14672374, 14738423, 14869751, 14935543, 15066872, 15132920, 15198713, 15330041,
            15396090, 15461882, 15593210, 15659003, 15725051, 15856380, 15922172, 16053500,
            16119549, 16185341, 16316670, 16382718, 16448510, 16579839, 16645631, 16777215,
        };

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB 튜플</returns>
        public (byte r, byte g, byte b) GetRGB(byte value)
        {
            byte[] byteArray = BitConverter.GetBytes(this.rgbArray[value]);

            return (byteArray[2], byteArray[1], byteArray[0]);
        }

        #endregion
    }
}

 

▶ BlueColorMap.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 청색 색상 맵
    /// </summary>
    public class BlueColorMap : IColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// ARGB 배열
        /// </summary>
        private readonly int[] argbArray =
        {
            -16767403, -16767402, -16767144, -16766887, -16701093, -16700835, -16700578, -16634784,
            -16634527, -16634269, -16568476, -16568218, -16568216, -16567959, -16502165, -16501908,
            -16501650, -16435856, -16435599, -16435341, -16369548, -16369290, -16369033, -16303495,
            -16303238, -16302980, -16237186, -16236929, -16236671, -16236414, -16170620, -16170363,
            -16170105, -16104312, -16104054, -16103797, -16038259, -16038002, -16037745, -15971951,
            -15971694, -15971436, -15905643, -15905385, -15905128, -15839335, -15839077, -15838820,
            -15773027, -15772769, -15772512, -15706718, -15706717, -15706460, -15706203, -15640409,
            -15640152, -15574359, -15574101, -15573844, -15508051, -15507794, -15507537, -15441743,
            -15441486, -15441229, -15375436, -15375179, -15309386, -15309128, -15308871, -15243078,
            -15242821, -15177284, -15177027, -15111234, -15110977, -15045184, -15044927, -14979134,
            -14978877, -14913084, -14912827, -14847034, -14781241, -14780984, -14715191, -14715190,
            -14649397, -14583605, -14517812, -14517555, -14451762, -14385969, -14320176, -14319920,
            -14254383, -14188590, -14122797, -14057005, -13991212, -13925419, -13859626, -13859626,
            -13793833, -13662505, -13596712, -13530919, -13465383, -13399590, -13333797, -13268005,
            -13202212, -13136676, -13005347, -12939555, -12873762, -12808226, -12676897, -12611105,
            -12545312, -12414240, -12348447, -12282655, -12151327, -12085790, -12019998, -11888669,
            -11822877, -11757341, -11626012, -11560220, -11429148, -11363355, -11232027, -11166235,
            -11035162, -10969370, -10903578, -10772505, -10706713, -10575385, -10509592, -10378520,
            -10312728, -10181400, -10115863, -09984535, -09918743, -09787414, -09721878, -09590550,
            -09524758, -09393429, -09327893, -09262101, -09130773, -09065237, -08933908, -08868116,
            -08736788, -08671252, -08605459, -08474131, -08408339, -08277267, -08211475, -08145682,
            -08014354, -07948818, -07817490, -07751698, -07685905, -07554833, -07489041, -07357713,
            -07291921, -07226128, -07095056, -07029264, -06897936, -06832144, -06766351, -06635279,
            -06569487, -06503695, -06372367, -06306575, -06175502, -06109710, -06043918, -05912590,
            -05846798, -05781261, -05649933, -05584141, -05518349, -05387021, -05321485, -05190156,
            -05124364, -05058572, -04927244, -04861452, -04730123, -04664587, -04598795, -04467467,
            -04401675, -04335883, -04204554, -04139018, -04007690, -03941898, -03876106, -03744777,
            -03678985, -03547657, -03481865, -03416329, -03285000, -03219208, -03087880, -03022088,
            -02956296, -02824968, -02759175, -02628103, -02562311, -02430983, -02365191, -02299398,
            -02168070, -02102278, -01970950, -01905158, -01839621, -01708293, -01642501, -01511173,
            -01445381, -01314052, -01248260, -01182468, -01051140, -00985604, -00854275, -00788483,
            -00657155, -00591363, -00525571, -00394242, -00328450, -00197122, -00131330, -00000001,
        };

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB 튜플</returns>
        public (byte r, byte g, byte b) GetRGB(byte value)
        {
            byte[] byteArray = BitConverter.GetBytes(this.argbArray[value]);

            return (byteArray[2], byteArray[1], byteArray[0]);
        }

        #endregion
    }
}

 

▶ GrayscaleColorMap.cs

namespace TestLibrary
{
    /// <summary>
    /// 회색조 색상 맵
    /// </summary>
    public class GrayscaleColorMap : IColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB</returns>
        public (byte r, byte g, byte b) GetRGB(byte value)
        {
            return (value, value, value);
        }

        #endregion
    }
}

 

▶ GrayscaleReversedColorMap.cs

namespace TestLibrary
{
    /// <summary>
    /// 회색조 반전 색상 맵
    /// </summary>
    public class GrayscaleReversedColorMap : IColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB</returns>
        public (byte r, byte g, byte b) GetRGB(byte value)
        {
            value = (byte)(255 - value);

            return (value, value, value);
        }

        #endregion
    }
}

 

▶ GreenColorMap.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 녹색 색상 맵
    /// </summary>
    public class GreenColorMap : IColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// ARGB 배열
        /// </summary>
        private readonly int[] argbArray =
        {
            -16761088, -16760832, -16760575, -16760318, -16760061, -16759804, -16759547, -16759290,
            -16759033, -16758776, -16758519, -16758006, -16757749, -16757492, -16757235, -16756979,
            -16756722, -16756465, -16756208, -16755951, -16755694, -16755437, -16755180, -16754667,
            -16754410, -16688617, -16688360, -16688104, -16687847, -16687590, -16687333, -16687076,
            -16621283, -16621026, -16620769, -16620512, -16620256, -16554463, -16554206, -16553949,
            -16553692, -16487899, -16487642, -16487385, -16421336, -16421080, -16420823, -16355030,
            -16354773, -16288980, -16288723, -16222930, -16222930, -16222673, -16156880, -16156623,
            -16090830, -16025038, -16024781, -15958988, -15958731, -15892938, -15827145, -15826889,
            -15761096, -15695303, -15695046, -15629254, -15563461, -15497924, -15497667, -15431875,
            -15366082, -15300289, -15234496, -15234240, -15168447, -15102654, -15037118, -14971325,
            -14905532, -14839740, -14773947, -14708154, -14642618, -14576825, -14511033, -14445240,
            -14379447, -14313655, -14248118, -14182326, -14116533, -14050741, -13985204, -13919412,
            -13853619, -13722291, -13656498, -13590962, -13525169, -13459377, -13328048, -13262512,
            -13196720, -13130927, -13065391, -12934063, -12868270, -12802478, -12671406, -12605613,
            -12539821, -12408749, -12342957, -12277164, -12146092, -12080300, -12014508, -11883435,
            -11817643, -11686315, -11620779, -11554986, -11423914, -11358122, -11226794, -11161258,
            -11029929, -10964137, -10833065, -10767273, -10636201, -10570408, -10439080, -10373544,
            -10242216, -10176680, -10045351, -09914023, -09848487, -09717159, -09651623, -09520294,
            -09389222, -09323430, -09192102, -09061029, -08995237, -08864165, -08798372, -08667300,
            -08535972, -08404643, -08339107, -08207779, -08076706, -08010914, -07879842, -07748513,
            -07682977, -07551648, -07420576, -07289248, -07223711, -07092383, -06961054, -06895518,
            -06764189, -06633116, -06501788, -06436251, -06304923, -06173850, -06108057, -05976984,
            -05845656, -05714583, -05648790, -05517717, -05386389, -05320596, -05189523, -05123730,
            -04992657, -04861328, -04795791, -04664462, -04598925, -04467596, -04336523, -04270730,
            -04139400, -04073863, -03942534, -03876997, -03745667, -03680130, -03614337, -03483007,
            -03417470, -03286140, -03220603, -03154809, -03023479, -02957942, -02892148, -02826610,
            -02760817, -02629487, -02563949, -02498155, -02432617, -02366823, -02301029, -02235491,
            -02169697, -02103903, -02038365, -01972571, -01906777, -01841239, -01775444, -01709650,
            -01644112, -01578318, -01512523, -01446985, -01381191, -01315396, -01249858, -01249599,
            -01183805, -01118266, -01052472, -00986678, -00986675, -00920880, -00855086, -00789547,
            -00723753, -00723494, -00657956, -00592161, -00526367, -00526108, -00460569, -00394775,
            -00394516, -00328977, -00263183, -00197388, -00197385, -00131591, -00065796, -00000001,

        };

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB</returns>
        public (byte r, byte g, byte b) GetRGB(byte value)
        {
            byte[] byteArray = BitConverter.GetBytes(this.argbArray[value]);

            return (byteArray[2], byteArray[1], byteArray[0]);
        }

        #endregion
    }
}

 

▶ InfernoColorMap.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 인페르노 색상 맵
    /// </summary>
    public class InfernoColorMap : IColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// RGB 배열
        /// </summary>
        private readonly int[] rgbArray =
        {
            00000003, 00000004, 00000006, 00065543, 00065801, 00065803, 00131342, 00131600,
            00197138, 00262932, 00262934, 00328728, 00394267, 00460061, 00525855, 00591393,
            00657187, 00722726, 00854056, 00919594, 00985389, 01050927, 01182258, 01247796,
            01313590, 01444665, 01510203, 01641278, 01706816, 01838147, 01903685, 02034759,
            02100298, 02231116, 02362190, 02493264, 02558802, 02689876, 02820694, 02951768,
            03017306, 03148380, 03279197, 03410271, 03475808, 03606881, 03737954, 03869028,
            03934565, 04065638, 04196710, 04262247, 04393576, 04524649, 04590185, 04721514,
            04852586, 04918379, 05049451, 05180780, 05246316, 05377644, 05443181, 05574509,
            05705581, 05771373, 05902701, 05968238, 06099566, 06230638, 06296430, 06427758,
            06493294, 06624622, 06690158, 06821486, 06952814, 07018350, 07149678, 07215214,
            07346542, 07477613, 07543405, 07674733, 07740269, 07871597, 08002669, 08068460,
            08199532, 08265324, 08396651, 08462187, 08593515, 08724586, 08790378, 08921450,
            08987241, 09118313, 09249641, 09315432, 09446504, 09512295, 09643367, 09774694,
            09840230, 09971557, 10037348, 10168420, 10234211, 10365283, 10496610, 10562401,
            10693473, 10759264, 10890335, 10956127, 11087454, 11218525, 11284316, 11415643,
            11481435, 11612506, 11678297, 11809624, 11875159, 12006486, 12072278, 12203605,
            12269396, 12400467, 12466258, 12532049, 12663376, 12729167, 12860494, 12926285,
            13057612, 13123147, 13188938, 13320265, 13386056, 13451847, 13583430, 13649220,
            13715011, 13780802, 13912129, 13977920, 14043711, 14109502, 14241085, 14306875,
            14372666, 14438457, 14504504, 14570295, 14636086, 14702132, 14833459, 14899250,
            14965297, 15031088, 15096878, 15097389, 15163180, 15229227, 15295018, 15361064,
            15426855, 15492902, 15558693, 15559203, 15625250, 15691041, 15757087, 15757342,
            15823389, 15889436, 15889690, 15955737, 15956248, 16022038, 16088085, 16088596,
            16154642, 16154897, 16220944, 16221454, 16287501, 16287756, 16288267, 16354313,
            16354824, 16355336, 16421127, 16421638, 16422150, 16422662, 16488710, 16489222,
            16489734, 16489991, 16490503, 16491016, 16491530, 16492043, 16492557, 16493070,
            16493584, 16494098, 16494612, 16494870, 16495384, 16495898, 16496412, 16496926,
            16431905, 16432419, 16432933, 16433448, 16368426, 16368940, 16369455, 16304433,
            16304948, 16305463, 16240442, 16240956, 16175935, 16176450, 16111429, 16111944,
            16046923, 16047183, 15982162, 15982678, 15983193, 15918173, 15918688, 15853668,
            15853928, 15854444, 15854960, 15855220, 15855737, 15856253, 15922049, 15922309,
            15988361, 16054157, 16119953, 16186005, 16251801, 16383133, 16448928, 16580260,
        };

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB</returns>
        public (byte r, byte g, byte b) GetRGB(byte value)
        {
            byte[] byteArray = BitConverter.GetBytes(this.rgbArray[value]);

            return (byteArray[2], byteArray[1], byteArray[0]);
        }

        #endregion
    }
}

 

▶ LoporaColorMap.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 로포라 색상 맵
    /// </summary>
    public class LoporaColorMap : IColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// RGB 배열
        /// </summary>
        private readonly int[] rgbArray =
        {
            0000000000, 0000069696, 0000137036, 0000203860, 0000270426, 0000336991, 0000403300, 0000469608,
            0000535915, 0000602222, 0000668273, 0000734580, 0000800631, 0000866681, 0000932987, 0000999037,
            0001065088, 0001131137, 0001197187, 0001262981, 0001329031, 0001395080, 0001461130, 0001526924,
            0001592973, 0001659023, 0001724816, 0001790865, 0001856659, 0001922708, 0001988501, 0002054550,
            0002120344, 0002186393, 0002252186, 0002317979, 0002384028, 0002449821, 0002515614, 0002581663,
            0002647456, 0002713249, 0002779042, 0002845091, 0002910884, 0002976677, 0003042470, 0003108263,
            0003174056, 0003240105, 0003305898, 0003371690, 0003437483, 0003503276, 0003569069, 0003634862,
            0003700654, 0003766447, 0003832240, 0003898033, 0003963825, 0004029618, 0004095411, 0004161204,
            0004227252, 0004292789, 0004358582, 0004424374, 0004490167, 0004555960, 0004621752, 0004687545,
            0004753338, 0004819130, 0004884923, 0004950716, 0005016508, 0005082301, 0005148093, 0005213886,
            0005279679, 0005345215, 0005411008, 0005476800, 0005542593, 0005608386, 0005674178, 0005739971,
            0005805763, 0005871300, 0005937092, 0006002885, 0006068677, 0006134470, 0006200263, 0006265799,
            0006331592, 0006397384, 0006463177, 0006528969, 0006594506, 0006660298, 0006726091, 0006791883,
            0006857676, 0006923212, 0006989005, 0007054797, 0007120590, 0007186126, 0007251918, 0007317711,
            0007383503, 0007449040, 0007514832, 0007580625, 0007646417, 0007711954, 0007777746, 0007843539,
            0007909331, 0007974867, 0008040660, 0008106452, 0008171989, 0008237781, 0008303574, 0008369366,
            0008434902, 0008435159, 0008500951, 0008566488, 0008632280, 0008698072, 0008763609, 0008829401,
            0008895194, 0008960986, 0009026522, 0009092315, 0009158107, 0009223644, 0009289436, 0009355228,
            0009420765, 0009486557, 0009552350, 0009617886, 0009683678, 0009749471, 0009815007, 0009880799,
            0009946336, 0010012128, 0010077921, 0010143457, 0010209249, 0010275042, 0010340578, 0010406370,
            0010472163, 0010537699, 0010603491, 0010669028, 0010734820, 0010800612, 0010866149, 0010931941,
            0010997734, 0011063270, 0011129062, 0011194599, 0011260391, 0011326183, 0011391720, 0011457512,
            0011523048, 0011588841, 0011654633, 0011720169, 0011785962, 0011851498, 0011917290, 0011983082,
            0012048619, 0012114411, 0012179947, 0012245740, 0012311532, 0012377068, 0012442861, 0012508397,
            0012574189, 0012639726, 0012705518, 0012771310, 0012836847, 0012902639, 0012968175, 0013033967,
            0013099504, 0013165296, 0013231088, 0013296625, 0013362417, 0013427953, 0013493746, 0013559282,
            0013625074, 0013690610, 0013756403, 0013822195, 0013887731, 0013953524, 0014019060, 0014084852,
            0014150388, 0014216181, 0014281717, 0014347509, 0014413046, 0014478838, 0014544374, 0014610166,
            0014675959, 0014741495, 0014807287, 0014872823, 0014938616, 0015004152, 0015069944, 0015135481,
            0015201273, 0015266809, 0015332601, 0015398138, 0015463930, 0015529466, 0015595258, 0015660795,
            0015726587, 0015792123, 0015857915, 0015923452, 0015989244, 0016054780, 0016120572, 0016186109,
            0016251901, 0016317437, 0016383229, 0016448766, 0016514558, 0016580350, 0016645887, 0016711679,
        };

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB</returns>
        public (byte r, byte g, byte b) GetRGB(byte value)
        {
            byte[] byteArray = BitConverter.GetBytes(this.rgbArray[value]);

            return (byteArray[2], byteArray[1], byteArray[0]);
        }

        #endregion
    }
}

 

▶ MagmaColorMap.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 마그마 색상 맵
    /// </summary>
    public class MagmaColorMap : IColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// RGB 배열
        /// </summary>
        private readonly int[] rgbArray =
        {
            00000003, 00000004, 00000006, 00065543, 00065801, 00065803, 00131597, 00131599,
            00197393, 00262931, 00263189, 00328727, 00394521, 00460059, 00525853, 00591647,
            00657186, 00722980, 00788774, 00854568, 00920106, 00985900, 01051695, 01117233,
            01183027, 01314101, 01379896, 01445434, 01511228, 01576767, 01708097, 01773636,
            01839174, 01970249, 02036043, 02101581, 02232656, 02298194, 02429269, 02494807,
            02625881, 02756956, 02822494, 02953312, 03084386, 03149925, 03280999, 03412072,
            03477354, 03608428, 03739502, 03870575, 03936113, 04067186, 04198259, 04329332,
            04394869, 04525942, 04657015, 04722808, 04853881, 04919417, 05050746, 05181819,
            05247611, 05378684, 05444476, 05575549, 05706877, 05772670, 05903742, 05969534,
            06100862, 06166399, 06297727, 06363263, 06494591, 06625920, 06691456, 06822784,
            06888576, 07019648, 07085440, 07216769, 07282305, 07413633, 07544705, 07610497,
            07741825, 07807361, 07938689, 08004225, 08135553, 08266881, 08332417, 08463745,
            08529281, 08660609, 08726145, 08857473, 08988801, 09054337, 09185664, 09251200,
            09382528, 09513600, 09579392, 09710464, 09776256, 09907327, 10038655, 10104191,
            10235519, 10366590, 10432382, 10563454, 10694782, 10760317, 10891645, 10957181,
            11088508, 11219836, 11285371, 11416699, 11547771, 11613562, 11744634, 11875961,
            11941497, 12072824, 12138360, 12269687, 12401015, 12466550, 12597877, 12728949,
            12794740, 12926068, 12991603, 13122930, 13254258, 13319793, 13451120, 13516912,
            13648239, 13714030, 13845101, 13910893, 14042220, 14108011, 14239338, 14305129,
            14436457, 14502248, 14568039, 14699366, 14765158, 14830949, 14962276, 15028323,
            15094114, 15159906, 15225953, 15357280, 15423072, 15489119, 15554911, 15620958,
            15621469, 15687261, 15753309, 15819100, 15885148, 15951196, 15951707, 16017499,
            16083547, 16084059, 16150107, 16150619, 16216411, 16216924, 16282972, 16283484,
            16349532, 16350045, 16350557, 16416606, 16416862, 16417375, 16483424, 16483936,
            16484449, 16484962, 16551011, 16551523, 16552036, 16552549, 16552806, 16618855,
            16619368, 16619881, 16620394, 16620907, 16621420, 16621934, 16622191, 16622704,
            16688753, 16689267, 16689780, 16690293, 16690806, 16691064, 16691577, 16692091,
            16692604, 16693117, 16693631, 16694144, 16694402, 16694915, 16695429, 16695942,
            16696456, 16696969, 16697227, 16697741, 16698254, 16633232, 16633746, 16634259,
            16634517, 16635031, 16635544, 16636058, 16636572, 16637085, 16637343, 16637857,
            16638371, 16573349, 16573862, 16574120, 16574634, 16575148, 16575662, 16576176,
            16576689, 16576947, 16577461, 16577975, 16512953, 16513467, 16513725, 16514239,
        };

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB</returns>
        public (byte r, byte g, byte b) GetRGB(byte value)
        {
            byte[] byteArray = BitConverter.GetBytes(this.rgbArray[value]);

            return (byteArray[2], byteArray[1], byteArray[0]);
        }

        #endregion
    }
}

 

▶ PlasmaColorMap.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 플라스마 색상 맵
    /// </summary>
    public class PlasmaColorMap : IColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// RGB 배열
        /// </summary>
        private readonly int[] rgbArray =
        {
            00788358, 01050503, 01246857, 01377930, 01574539, 01771148, 01902221, 02033038,
            02164111, 02295184, 02426257, 02557330, 02688403, 02819476, 02950292, 03081365,
            03212438, 03343511, 03409048, 03540120, 03671193, 03802266, 03867546, 03998619,
            04129692, 04195228, 04326301, 04457374, 04522910, 04653727, 04784799, 04850336,
            04981409, 05112481, 05178018, 05308834, 05374371, 05505443, 05636515, 05702052,
            05833124, 05898405, 06029477, 06160549, 06226086, 06357158, 06422694, 06553767,
            06619303, 06750375, 06815911, 06946983, 07078056, 07143592, 07274664, 07340200,
            07471272, 07536808, 07667880, 07733672, 07864744, 07930280, 08061608, 08127143,
            08258471, 08324007, 08455335, 08520871, 08652198, 08717990, 08783782, 08914853,
            08980645, 09111972, 09177764, 09309348, 09375139, 09440931, 09572258, 09638049,
            09769377, 09835168, 09900960, 10032287, 10098078, 10164126, 10295453, 10361244,
            10427035, 10492827, 10624154, 10689945, 10755736, 10821527, 10953111, 11018902,
            11084693, 11150484, 11281811, 11347602, 11413393, 11479184, 11545231, 11611023,
            11676814, 11808141, 11873932, 11939723, 12005514, 12071561, 12137352, 12203143,
            12268934, 12334725, 12400516, 12466307, 12532098, 12598145, 12663936, 12729728,
            12795519, 12861310, 12927101, 12992892, 13058683, 13124730, 13190521, 13256312,
            13322103, 13387894, 13453685, 13519477, 13585268, 13651315, 13717106, 13717361,
            13783152, 13848943, 13914734, 13980525, 14046573, 14112364, 14112619, 14178410,
            14244201, 14309992, 14375783, 14441830, 14442086, 14507877, 14573668, 14639459,
            14639714, 14705761, 14771552, 14837344, 14903135, 14903390, 14969437, 15035228,
            15035483, 15101274, 15167066, 15233113, 15233368, 15299159, 15364950, 15365205,
            15431252, 15497044, 15497299, 15563090, 15563601, 15629392, 15695183, 15695438,
            15761485, 15761741, 15827532, 15893579, 15893834, 15959625, 15959880, 16025927,
            16026183, 16091974, 16092485, 16158276, 16158531, 16159042, 16224833, 16225089,
            16291136, 16291391, 16291902, 16357693, 16357948, 16423995, 16424250, 16424762,
            16425017, 16491064, 16491319, 16491574, 16557621, 16557877, 16558388, 16558643,
            16559154, 16559409, 16625457, 16625712, 16626223, 16626478, 16626989, 16627245,
            16627756, 16628011, 16628523, 16628778, 16629289, 16629801, 16630056, 16630568,
            16630823, 16631334, 16566054, 16566566, 16567077, 16567333, 16567845, 16502820,
            16503076, 16503588, 16438564, 16438820, 16439332, 16374052, 16374564, 16309540,
            16310052, 16244772, 16245285, 16180261, 16180517, 16115494, 16116006, 16050726,
            15985702, 15986214, 15921190, 15921446, 15856422, 15791397, 15791651, 15726625,
        };

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB</returns>
        public (byte r, byte g, byte b) GetRGB(byte value)
        {
            byte[] byteArray = BitConverter.GetBytes(this.rgbArray[value]);

            return (byteArray[2], byteArray[1], byteArray[0]);
        }

        #endregion
    }
}

 

▶ TurboColorMap.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 터보 색상 맵
    /// </summary>
    public class TurboColorMap : IColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// ARGB 배열
        /// </summary>
        private readonly int[] argbArray =
        {
            -13559489, -13493436, -13427382, -13361328, -13295018, -13228964, -13162911, -13096857,
            -13030547, -12964493, -12898440, -12832130, -12766077, -12700023, -12633970, -12567660,
            -12501607, -12435554, -12369245, -12303192, -12237139, -12171086, -12170313, -12104260,
            -12038208, -12037436, -11971383, -11905331, -11904559, -11838507, -11837991, -11771940,
            -11771168, -11770653, -11770138, -11703831, -11703316, -11702801, -11702287, -11701517,
            -11701003, -11700489, -11765255, -11764742, -11764228, -11828995, -11828482, -11893506,
            -11892737, -11957761, -12022785, -12022017, -12087042, -12152067, -12217092, -12347397,
            -12412423, -12477448, -12542218, -12672780, -12737806, -12802577, -12933139, -12998166,
            -13128729, -13193500, -13324063, -13389091, -13519654, -13584682, -13714989, -13780017,
            -13910581, -13975609, -14106173, -14171201, -14301765, -14366793, -14431822, -14562386,
            -14627414, -14692442, -14757471, -14822499, -14887527, -14952556, -14952048, -15017332,
            -15082361, -15081853, -15147137, -15146629, -15146121, -15145869, -15145361, -15145109,
            -15079065, -15078812, -15013024, -15012515, -14946726, -14880938, -14749356, -14683567,
            -14617778, -14486453, -14355127, -14223801, -14092475, -13961405, -13764543, -13633217,
            -13436355, -13239748, -13108422, -12911559, -12714952, -12518089, -12255946, -12059339,
            -11862476, -11600333, -11403725, -11141582, -10879182, -10682575, -10420431, -10158287,
            -09896143, -09633999, -09372111, -09175503, -08913359, -08651215, -08389327, -08127183,
            -07865294, -07603150, -07341262, -07079117, -06817229, -06555341, -06293452, -06031308,
            -05834955, -05573067, -05311178, -05114826, -04852937, -04656585, -04394952, -04198600,
            -04002247, -03740358, -03544262, -03347910, -03217349, -03020997, -02824900, -02628804,
            -02497987, -02301891, -02171331, -02040770, -01910210, -01779394, -01648834, -01518273,
            -01387713, -01257153, -01126849, -01061824, -00931264, -00866240, -00801216, -00670656,
            -00605888, -00540864, -00475840, -00411072, -00346048, -00346560, -00281792, -00282304,
            -00217536, -00218048, -00153280, -00153793, -00154561, -00155073, -00155841, -00156354,
            -00157122, -00157634, -00158403, -00224451, -00225219, -00291524, -00292036, -00358341,
            -00424389, -00490694, -00491206, -00557511, -00623559, -00755400, -00821705, -00887754,
            -00954058, -01085643, -01151948, -01283533, -01349837, -01481422, -01613263, -01679312,
            -01811153, -01942738, -02074579, -02206164, -02337749, -02469590, -02601175, -02733016,
            -02930137, -03061978, -03193563, -03390940, -03522526, -03654111, -03851488, -03983073,
            -04180450, -04377572, -04509157, -04706534, -04838119, -05035497, -05232618, -05429739,
            -05561580, -05758702, -05956079, -06153200, -06350322, -06482163, -06679284, -06876662,
            -07073783, -07270904, -07468282, -07665403, -07862524, -08059902, -08257023, -08388608,
        };

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB</returns>
        public (byte r, byte g, byte b) GetRGB(byte value)
        {
            byte[] byteArray = BitConverter.GetBytes(this.argbArray[value]);

            return (byteArray[2], byteArray[1], byteArray[0]);
        }

        #endregion
    }
}

 

▶ ViridisColorMap.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 비리디스 색상 맵
    /// </summary>
    public class ViridisColorMap : IColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// RGB 배열
        /// </summary>
        private readonly int[] rgbArray =
        {
            04456788, 04457045, 04457303, 04523352, 04523610, 04524123, 04589916, 04590430,
            04590687, 04591201, 04656994, 04657507, 04657765, 04658278, 04658535, 04658793,
            04659306, 04725099, 04725356, 04725870, 04726127, 04726384, 04726897, 04727154,
            04727411, 04727668, 04662645, 04662902, 04663159, 04663416, 04663929, 04664186,
            04664443, 04599164, 04599676, 04599933, 04600190, 04534911, 04535423, 04535680,
            04535937, 04470657, 04471170, 04405891, 04406147, 04406404, 04341124, 04341381,
            04341893, 04276614, 04276870, 04211591, 04211847, 04146567, 04147080, 04081800,
            04082057, 04016777, 04017033, 04017289, 03952010, 03952266, 03887242, 03887498,
            03822219, 03822475, 03757195, 03757451, 03692171, 03692428, 03627148, 03627404,
            03562124, 03562380, 03497100, 03497356, 03432077, 03432333, 03367053, 03367309,
            03302029, 03302285, 03237005, 03237261, 03237517, 03172237, 03172493, 03107213,
            03107469, 03042190, 03042446, 03042702, 02977422, 02977678, 02912398, 02912654,
            02912910, 02847630, 02847886, 02782606, 02782862, 02783118, 02717838, 02718094,
            02652814, 02652814, 02653070, 02587790, 02588046, 02588302, 02523022, 02523278,
            02523534, 02458254, 02458509, 02393229, 02393485, 02393741, 02328461, 02328717,
            02328973, 02263437, 02263693, 02263949, 02198669, 02198924, 02199180, 02133900,
            02134156, 02134412, 02069132, 02069387, 02069643, 02069899, 02070155, 02004874,
            02005130, 02005386, 02005386, 02005641, 02005897, 02006153, 02006408, 02006664,
            02006920, 02007175, 02072967, 02073222, 02073478, 02139269, 02139525, 02205317,
            02205572, 02271108, 02336899, 02337154, 02402946, 02468737, 02534529, 02600320,
            02666111, 02731903, 02797694, 02863485, 02929021, 03060348, 03126139, 03191930,
            03323258, 03389049, 03520376, 03586167, 03717494, 03783030, 03914357, 04045684,
            04111475, 04242802, 04374129, 04505200, 04570991, 04702318, 04833645, 04964972,
            05096043, 05227369, 05358696, 05490023, 05621350, 05752421, 05883748, 06015074,
            06211937, 06343008, 06474335, 06605661, 06802524, 06933595, 07064921, 07196248,
            07392854, 07524181, 07655508, 07852114, 07983441, 08180303, 08311374, 08508236,
            08639307, 08836169, 08967495, 09164102, 09295428, 09492035, 09623361, 09819967,
            09951294, 10147900, 10344762, 10475832, 10672695, 10869301, 11000627, 11197234,
            11394096, 11525166, 11722028, 11918635, 12049705, 12246567, 12443174, 12574500,
            12771106, 12967713, 13099039, 13295646, 13492253, 13623580, 13820187, 13951258,
            14148121, 14344728, 14475800, 14672664, 14803736, 15000344, 15197209, 15328281,
            15524890, 15656219, 15852828, 15983902, 16180767, 16311841, 16442914, 16639780,
        };

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB</returns>
        public (byte r, byte g, byte b) GetRGB(byte value)
        {
            byte[] byteArray = BitConverter.GetBytes(this.rgbArray[value]);

            return (byteArray[2], byteArray[1], byteArray[0]);
        }

        #endregion
    }
}

 

▶ BitmapHelper.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace TestLibrary
{
    /// <summary>
    /// 비트맵 헬퍼
    /// </summary>
    public static class BitmapHelper
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 비트맵 구하기 - GetBitmap(fftList, colorMap, intensity, db, dbScale, roll, rollOffset)

        /// <summary>
        /// 비트맵 구하기
        /// </summary>
        /// <param name="fftList">FFT 리스트</param>
        /// <param name="colorMap">색상 맵</param>
        /// <param name="intensity">강도</param>
        /// <param name="db">dB 적용 여부</param>
        /// <param name="dbScale">dB 스케일</param>
        /// <param name="roll">롤 여부</param>
        /// <param name="rollOffset">롤 오프셋</param>
        /// <returns>비트맵</returns>
        public static Bitmap GetBitmap
        (
            List<double[]> fftList,
            ColorMap       colorMap,
            double         intensity = 1,
            bool           db = false,
            double         dbScale = 1,
            bool           roll = false,
            int            rollOffset = 0
        )
        {
            if(fftList.Count == 0)
            {
                throw new ArgumentException("This Spectrogram contains no FFTs (likely because no signal was added)");
            }

            int width  = fftList.Count;
            int height = fftList[0].Length;

            Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format8bppIndexed);

            colorMap.SetBitmapPalette(bitmap);

            Rectangle lockRectangle = new Rectangle(0, 0, width, height);

            BitmapData bitmapData = bitmap.LockBits(lockRectangle, ImageLockMode.ReadOnly, bitmap.PixelFormat);

            int stride = bitmapData.Stride;

            byte[] byteArray = new byte[bitmapData.Stride * bitmap.Height];

            Parallel.For
            (
                0,
                width,
                column =>
                {
                    int sourceColumn = column;

                    if(roll)
                    {
                        sourceColumn += width - rollOffset % width;

                        if(sourceColumn >= width)
                        {
                            sourceColumn -= width;
                        }
                    }

                    for(int row = 0; row < height; row++)
                    {
                        double value = fftList[sourceColumn][row];

                        if(db)
                        {
                            value = 20 * Math.Log10(value * dbScale + 1);
                        }

                        value *= intensity;

                        value = Math.Min(value, 255);

                        int bytePosition = (height - 1 - row) * stride + column;

                        byteArray[bytePosition] = (byte)value;
                    }
                }
            );

            Marshal.Copy(byteArray, 0, bitmapData.Scan0, byteArray.Length);

            bitmap.UnlockBits(bitmapData);

            return bitmap;
        }

        #endregion
        #region 수직 스케일 비트맵 구하기 - GetVerticalScaleBitmap(width, setting, hzOffset, tickSize, reduction)

        /// <summary>
        /// 수직 스케일 비트맵 구하기
        /// </summary>
        /// <param name="width">너비</param>
        /// <param name="setting">설정</param>
        /// <param name="hzOffset">HZ 오프셋</param>
        /// <param name="tickSize">틱 크기</param>
        /// <param name="reduction">축소</param>
        /// <returns>수직 스케일 비트맵</returns>
        public static Bitmap GetVerticalScaleBitmap(int width, Setting setting, int hzOffset = 0, int tickSize = 3, int reduction = 1)
        {
            double   hzTick              = 1;
            int      minimumSpacingPixel = 50;
            double[] multiplierArray     = { 2, 2.5, 2 };
            int      multiplier          = 0;

            while(true)
            {
                hzTick *= multiplierArray[multiplier++ % multiplierArray.Length];

                double tickCount         = setting.FrequencySpan / hzTick;
                double pixelBetweenTicks = setting.Height / tickCount;

                if(pixelBetweenTicks >= minimumSpacingPixel * reduction)
                {
                    break;
                }
            }

            Bitmap bitmap = new Bitmap(width, setting.Height / reduction, PixelFormat.Format32bppPArgb);

            using(Graphics     graphics     = Graphics.FromImage(bitmap))
            using(Pen          pen          = new Pen(Color.Black))
            using(SolidBrush   brush        = new SolidBrush(Color.Black))
            using(Font         font         = new Font("나눔고딕코딩", 12))
            using(StringFormat stringFormat = new StringFormat() { LineAlignment = StringAlignment.Center })
            {
                graphics.Clear(Color.White);

                List<double> frequencyList = new List<double>();

                for(double frequency = setting.MinimumFrequency; frequency <= setting.MaximumFrequency; frequency += hzTick)
                {
                    frequencyList.Add(frequency);
                }

                if(frequencyList.Count >= 2)
                {
                    frequencyList.RemoveAt(0);

                    frequencyList.RemoveAt(frequencyList.Count - 1);
                }

                foreach(double frequency in frequencyList)
                {
                    int y = setting.GetPixelY(frequency) / reduction;

                    graphics.DrawLine(pen, 0, y, tickSize, y);

                    graphics.DrawString($"{frequency + hzOffset:N0} Hz", font, brush, tickSize, y, stringFormat);
                }
            }

            return bitmap;
        }

        #endregion
    }
}

 

▶ ColorMap.cs

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;

namespace TestLibrary
{
    /// <summary>
    /// 색상 맵
    /// </summary>
    public class ColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 아르고 색상 맵 - Argo

        /// <summary>
        /// 아르고 색상 맵
        /// </summary>
        public static ColorMap Argo => new ColorMap(new ArgoColorMap());

        #endregion
        #region 청색 색상 맵 - Blue

        /// <summary>
        /// 청색 색상 맵
        /// </summary>
        public static ColorMap Blue => new ColorMap(new BlueColorMap());

        #endregion
        #region 회색조 색상 맵 - Grayscale

        /// <summary>
        /// 회색조 색상 맵
        /// </summary>
        public static ColorMap Grayscale => new ColorMap(new GrayscaleColorMap());

        #endregion
        #region 회색조 반전 색상 맵 - GrayscaleReversed

        /// <summary>
        /// 회색조 반전 색상 맵
        /// </summary>
        public static ColorMap GrayscaleReversed => new ColorMap(new GrayscaleReversedColorMap());

        #endregion
        #region 녹색 색상 맵 - Green

        /// <summary>
        /// 녹색 색상 맵
        /// </summary>
        public static ColorMap Green => new ColorMap(new GreenColorMap());

        #endregion
        #region 인페르노 색상 맵 - Inferno

        /// <summary>
        /// 인페르노 색상 맵
        /// </summary>
        public static ColorMap Inferno => new ColorMap(new InfernoColorMap());

        #endregion
        #region 로포라 색상 맵 - Lopora

        /// <summary>
        /// 로포라 색상 맵
        /// </summary>
        public static ColorMap Lopora => new ColorMap(new LoporaColorMap());

        #endregion
        #region 마그마 색상 맵 - Magma

        /// <summary>
        /// 마그마 색상 맵
        /// </summary>
        public static ColorMap Magma => new ColorMap(new MagmaColorMap());

        #endregion
        #region 플라스마 색상 맵 - Plasma

        /// <summary>
        /// 플라스마 색상 맵
        /// </summary>
        public static ColorMap Plasma => new ColorMap(new PlasmaColorMap());

        #endregion
        #region 터보 색상 맵 - Turbo

        /// <summary>
        /// 터보 색상 맵
        /// </summary>
        public static ColorMap Turbo => new ColorMap(new TurboColorMap());

        #endregion
        #region 비리디스 색상 맵 - Viridis

        /// <summary>
        /// 비리디스 색상 맵
        /// </summary>
        public static ColorMap Viridis => new ColorMap(new ViridisColorMap());

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 색상 맵
        /// </summary>
        private readonly IColorMap colorMap;

        /// <summary>
        /// 명칭
        /// </summary>
        public readonly string Name;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - ColorMap(colorMap)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="colorMap">색상 맵</param>
        public ColorMap(IColorMap colorMap)
        {
            this.colorMap = colorMap ?? new GrayscaleColorMap();

            Name = this.colorMap.GetType().Name;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 색상 맵 배열 구하기 - GetColorMapArray()

        /// <summary>
        /// 색상 맵 배열 구하기
        /// </summary>
        /// <returns>색상 맵 배열</returns>
        public static ColorMap[] GetColorMapArray() => typeof(ColorMap).GetProperties()
                                                                       .Select(x => (ColorMap)x.GetValue(x.Name))
                                                                       .ToArray();

        #endregion
        #region 색상 맵 명칭 배열 구하기 - GetColorMapNameArray()

        /// <summary>
        /// 색상 맵 명칭 배열 구하기
        /// </summary>
        /// <returns>색상 맵 명칭 배열</returns>
        public static string[] GetColorMapNameArray()
        {
            return GetColorMapArray().Select(x => x.Name).ToArray();
        }

        #endregion
        #region 색상 맵 구하기 - GetColorMap(colorMapName)

        /// <summary>
        /// 색상 맵 구하기
        /// </summary>
        /// <param name="colorMapName">색상 맵 명칭</param>
        /// <returns>색상 맵</returns>
        public static ColorMap GetColorMap(string colorMapName)
        {
            foreach(ColorMap colorMap in GetColorMapArray())
            {
                if(string.Equals(colorMap.Name, colorMapName, StringComparison.InvariantCultureIgnoreCase))
                {
                    return colorMap;
                }
            }

            throw new ArgumentException($"ColorMap does not exist : {colorMapName}");
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 문자열 구하기 - ToString()

        /// <summary>
        /// 문자열 구하기
        /// </summary>
        /// <returns>문자열</returns>
        public override string ToString()
        {
            return $"ColorMap {Name}";
        }

        #endregion
        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB 튜플</returns>
        public (byte r, byte g, byte b) GetRGB(byte value)
        {
            return this.colorMap.GetRGB(value);
        }

        #endregion
        #region RGB 구하기 - GetRGB(fraction)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="fraction">분수</param>
        /// <returns>RGB 튜플</returns>
        public (byte r, byte g, byte b) GetRGB(double fraction)
        {
            fraction = Math.Max(fraction, 0);
            fraction = Math.Min(fraction, 1);

            return this.colorMap.GetRGB((byte)(fraction * 255));
        }

        #endregion
        #region 32비트 정수 구하기 - GetInt32(value)

        /// <summary>
        /// 32비트 정수 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>32비트 정수</returns>
        public int GetInt32(byte value)
        {
            var (r, g, b) = GetRGB(value);

            return 255 << 24 | r << 16 | g << 8 | b;
        }

        #endregion
        #region 32비트 정수 구하기 - GetInt32(fraction)

        /// <summary>
        /// 32비트 정수 구하기
        /// </summary>
        /// <param name="fraction">분수</param>
        /// <returns>32비트 정수</returns>
        public int GetInt32(double fraction)
        {
            var (r, g, b) = GetRGB(fraction);

            return 255 << 24 | r << 16 | g << 8 | b;
        }

        #endregion
        #region 색상 구하기 - GetColor(value)

        /// <summary>
        /// 색상 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>색상</returns>
        public Color GetColor(byte value)
        {
            return Color.FromArgb(GetInt32(value));
        }

        #endregion
        #region 색상 구하기 - GetColor(fraction)

        /// <summary>
        /// 색상 구하기
        /// </summary>
        /// <param name="fraction">분수</param>
        /// <returns>색상</returns>
        public Color GetColor(double fraction)
        {
            return Color.FromArgb(GetInt32(fraction));
        }

        #endregion
        #region 비트맵 팔레트 설정하기 - SetBitmapPalette(bitmap)

        /// <summary>
        /// 비트맵 팔레트 설정하기
        /// </summary>
        /// <param name="bitmap">비트맵</param>
        public void SetBitmapPalette(Bitmap bitmap)
        {
            ColorPalette palette = bitmap.Palette;

            for(int i = 0; i < 256; i++)
            {
                palette.Entries[i] = GetColor((byte)i);
            }

            bitmap.Palette = palette;
        }

        #endregion
    }
}

 

▶ IColorMap.cs

namespace TestLibrary
{
    /// <summary>
    /// 색상 맵 인터페이스
    /// </summary>
    public interface IColorMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        #region RGB 구하기 - GetRGB(value)

        /// <summary>
        /// RGB 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>RGB 튜플</returns>
        (byte r, byte g, byte b) GetRGB(byte value);

        #endregion
    }
}

 

▶ Setting.cs

using System;

using FftSharp;
using FftSharp.Windows;

namespace TestLibrary
{
    /// <summary>
    /// 설정
    /// </summary>
    public class Setting
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 샘플 비율
        /// </summary>
        public readonly int SampleRate;

        /// <summary>
        /// FFT 크기
        /// </summary>
        public readonly int FFTSize;

        /// <summary>
        /// FFT 길이 (단위 : 초)
        /// </summary>
        public readonly double FFTLength;

        /// <summary>
        /// 주파수 나이퀴스트
        /// </summary>
        public readonly double FrequencyNyquist;

        /// <summary>
        /// 픽셀당 HZ
        /// </summary>
        public readonly double HZPerPixel;

        /// <summary>
        /// HZ당 픽셀
        /// </summary>
        public readonly double PixelPerHZ;

        /// <summary>
        /// FFT 인덱스 1
        /// </summary>
        public readonly int FFTIndex1;

        /// <summary>
        /// FFT 인덱스 2
        /// </summary>
        public readonly int FFTIndex2;

        /// <summary>
        /// 최소 주파수
        /// </summary>
        public readonly double MinimumFrequency;

        /// <summary>
        /// 최대 주파수
        /// </summary>
        public readonly double MaximumFrequency;

        /// <summary>
        /// 주파수 범위
        /// </summary>
        public readonly double FrequencySpan;

        /// <summary>
        /// 높이
        /// </summary>
        public readonly int Height;

        /// <summary>
        /// HZ 오프셋
        /// </summary>
        public int HZOffset;

        /// <summary>
        /// 윈도우 배열
        /// </summary>
        public readonly double[] WindowArray;

        /// <summary>
        /// 단계 크기
        /// </summary>
        public readonly int StepSize;

        /// <summary>
        /// 단계 길이 (단위 : 초)
        /// </summary>
        public readonly double StepLength;

        /// <summary>
        /// 단계 오버랩 (분수)
        /// </summary>
        public readonly double StepOverlapFraction;

        /// <summary>
        /// 단계 오버랩 (단위 : 초)
        /// </summary>
        public readonly double StepOverlap;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - Setting(sampleRate, fftSize, stepSize, minimumFrequency, maximumFrequency, hzOffset)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="sampleRate">샘플 비율</param>
        /// <param name="fftSize">FFT 크기</param>
        /// <param name="stepSize">단계 크기</param>
        /// <param name="minimumFrequency">최소 주파수</param>
        /// <param name="maximumFrequency">최대 주파수</param>
        /// <param name="hzOffset">HZ 오프셋</param>
        public Setting(int sampleRate, int fftSize, int stepSize, double minimumFrequency, double maximumFrequency, int hzOffset)
        {
            if(Transform.IsPowerOfTwo(fftSize) == false)
            {
                throw new ArgumentException("FFT size must be a power of 2");
            }

            SampleRate          = sampleRate;
            FFTSize             = fftSize;
            StepSize            = stepSize;
            FFTLength           = (double)fftSize / sampleRate;
            minimumFrequency    = Math.Max(minimumFrequency, 0);
            FrequencyNyquist    = sampleRate / 2;
            HZPerPixel          = (double)sampleRate / fftSize;
            PixelPerHZ          = (double)fftSize / sampleRate;
            FFTIndex1           = (minimumFrequency == 0) ? 0 : (int)(minimumFrequency / HZPerPixel);
            FFTIndex2           = (maximumFrequency >= FrequencyNyquist) ? fftSize / 2 : (int)(maximumFrequency / HZPerPixel);
            Height              = FFTIndex2 - FFTIndex1;
            MinimumFrequency    = FFTIndex1 * HZPerPixel;
            MaximumFrequency    = FFTIndex2 * HZPerPixel;
            FrequencySpan       = MaximumFrequency - MinimumFrequency;
            HZOffset            = hzOffset;
            StepLength          = (double)StepSize / sampleRate;
            WindowArray         = new Hanning().Create(fftSize);
            StepOverlap         = FFTLength - StepLength;
            StepOverlapFraction = StepOverlap / FFTLength;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 픽셀 Y 구하기 - GetPixelY(frequency)

        /// <summary>
        /// 픽셀 Y 구하기
        /// </summary>
        /// <param name="frequency">주파수</param>
        /// <returns>픽셀 Y</returns>
        public int GetPixelY(double frequency)
        {
            return (int)(Height - (frequency - MinimumFrequency + HZPerPixel) * PixelPerHZ - 1);
        }

        #endregion
    }
}

 

▶ SpectrogramFileFormat.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;

using FftSharp;

namespace TestLibrary
{
    /// <summary>
    /// 스펙트로그램 파일 포맷
    /// </summary>
    public class SpectrogramFileFormat
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 메이저 버전
        /// </summary>
        public readonly byte MajorVersion = 1;

        /// <summary>
        /// 마이너 버전
        /// </summary>
        public readonly byte MinorVersion = 1;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 파일 경로 - FilePath

        /// <summary>
        /// 파일 경로
        /// </summary>
        public string FilePath { get; private set; }

        #endregion
        #region 샘플 비율 - SampleRate

        /// <summary>
        /// 샘플 비율
        /// </summary>
        public int SampleRate { get; private set; }

        #endregion
        #region 단계 크기 - StepSize

        /// <summary>
        /// 단계 크기
        /// </summary>
        public int StepSize { get; private set; }

        #endregion
        #region 너비 - Width

        /// <summary>
        /// 너비
        /// </summary>
        public int Width { get; private set; }

        #endregion
        #region FFT 크기 - FFTSize

        /// <summary>
        /// FFT 크기
        /// </summary>
        public int FFTSize { get; private set; }

        #endregion
        #region FFT 첫번째 인덱스 - FFTFirstIndex

        /// <summary>
        /// FFT 첫번째 인덱스
        /// </summary>
        public int FFTFirstIndex { get; private set; }

        #endregion
        #region 높이 - Height

        /// <summary>
        /// 높이
        /// </summary>
        public int Height { get; private set; }

        #endregion
        #region HZ 오프셋 - HZOffset

        /// <summary>
        /// HZ 오프셋
        /// </summary>
        public int HZOffset { get; private set; }

        #endregion
        #region MEL BIN 카운트 - MELBINCount

        /// <summary>
        /// MEL BIN 카운트
        /// </summary>
        public int MELBINCount { get; private set; }

        #endregion
        #region 데시빌 카운트 - DecibelCount

        /// <summary>
        /// 데시빌 카운트
        /// </summary>
        public bool DecibelCount { get; private set; }

        #endregion
        #region MEL 여부 - IsMEL

        /// <summary>
        /// MEL 여부
        /// </summary>
        public bool IsMEL
        {
            get
            {
                return MELBINCount > 0;
            }
        }

        #endregion
        #region FFT 리스트 - FFTList

        /// <summary>
        /// FFT 리스트
        /// </summary>
        public List<double[]> FFTList { get; private set; }

        #endregion
        #region 이미지 너비 - ImageWidth

        /// <summary>
        /// 이미지 너비
        /// </summary>
        public int ImageWidth { get { return (FFTList is null) ? 0 : FFTList.Count; } }

        #endregion
        #region 이미지 높이 - ImageHeight

        /// <summary>
        /// 이미지 높이
        /// </summary>
        public int ImageHeight { get { return (FFTList is null) ? 0 : FFTList[0].Length; } }

        #endregion
        #region 시간 배열 - TimeArray

        /// <summary>
        /// 시간 배열
        /// </summary>
        public double[] TimeArray { get; private set; }

        #endregion
        #region 주파수 배열 - FrequencyArray

        /// <summary>
        /// 주파수 배열
        /// </summary>
        public double[] FrequencyArray { get; private set; }

        #endregion
        #region MEL 배열 - MELArray

        /// <summary>
        /// MEL 배열
        /// </summary>
        public double[] MELArray { get; private set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - SpectrogramFileFormat()

        /// <summary>
        /// 생성자
        /// </summary>
        public SpectrogramFileFormat()
        {
        }

        #endregion
        #region 생성자 - SpectrogramFileFormat(filePath)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="filePath">파일 경로</param>
        public SpectrogramFileFormat(string filePath)
        {
            Load(filePath);

            CalculateTimes();

            CalculateFrequencies();
        }

        #endregion
        #region 생성자 - SpectrogramFileFormat(generator, melBinCount)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="generator">스펙트로그램 제너레이터</param>
        /// <param name="melBinCount">MEL BIN 카운트</param>
        public SpectrogramFileFormat(SpectrogramGenerator generator, int melBinCount = 0)
        {
            SampleRate    = generator.SampleRate;
            StepSize      = generator.StepSize;
            Width         = generator.Width;
            FFTSize       = generator.FFTSize;
            FFTFirstIndex = generator.NextColumnIndex;
            Height        = generator.Height;
            HZOffset      = generator.HZOffset;
            MELBINCount   = melBinCount;
            FFTList       = (melBinCount > 0) ? generator.GetMELFFTArrayList(melBinCount) : generator.GetFFTArrayList();

            CalculateTimes();

            CalculateFrequencies();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 문자열 구하기 - ToString()

        /// <summary>
        /// 문자열 구하기
        /// </summary>
        /// <returns>문자열</returns>
        public override string ToString()
        {
            return $"SFF {ImageWidth}X{ImageHeight}";
        }

        #endregion
        #region 비트맵 구하기 - GetBitmap(colorMap, intensity, db)

        /// <summary>
        /// 비트맵 구하기
        /// </summary>
        /// <param name="colorMap">색상 맵</param>
        /// <param name="intensity">강도</param>
        /// <param name="db">데시빌 여부</param>
        /// <returns>비트맵</returns>
        public Bitmap GetBitmap(ColorMap colorMap = null, double intensity = 1, bool db = false)
        {
            colorMap = colorMap ?? ColorMap.Viridis;

            return BitmapHelper.GetBitmap(FFTList, colorMap, intensity, db);
        }

        #endregion
        #region 로드하기 - Load(filePath)

        /// <summary>
        /// 로드하기
        /// </summary>
        /// <param name="filePath">파일 경로</param>
        public void Load(string filePath)
        {
            FilePath = Path.GetFullPath(filePath);

            byte[] byteArray = File.ReadAllBytes(filePath);

            int magicNumber = BitConverter.ToInt32(byteArray, 0);

            if(magicNumber != 1179014099)
            {
                throw new InvalidDataException("not a valid SFF file");
            }

            SampleRate    = BitConverter.ToInt32(byteArray, 42);
            StepSize      = BitConverter.ToInt32(byteArray, 46);
            Width         = BitConverter.ToInt32(byteArray, 50);
            FFTSize       = BitConverter.ToInt32(byteArray, 54);
            FFTFirstIndex = BitConverter.ToInt32(byteArray, 58);
            Height        = BitConverter.ToInt32(byteArray, 62);
            HZOffset      = BitConverter.ToInt32(byteArray, 66);
            MELBINCount   = BitConverter.ToInt32(byteArray, 84);

            byte valueCountPerPoint = byteArray[70];
            bool isComplex          = valueCountPerPoint == 2;

            if(isComplex)
            {
                throw new NotImplementedException("complex data is not yet supported");
            }

            byte byteCountPerValue = byteArray[71];

            DecibelCount = byteArray[72] == 1;

            int firstDataByte = (int)BitConverter.ToUInt32(byteArray, 80);

            MELBINCount = BitConverter.ToInt32(byteArray, 84);

            int FFTHeight = BitConverter.ToInt32(byteArray, 88);
            int FFTWidth  = BitConverter.ToInt32(byteArray, 92);

            FFTList = new List<double[]>();

            int byteCountPerPoint  = byteCountPerValue * valueCountPerPoint;
            int byteCountPerColumn = FFTHeight * byteCountPerPoint;

            for(int x = 0; x < FFTWidth; x++)
            {
                FFTList.Add(new double[FFTHeight]);

                int columnOffset = byteCountPerColumn * x;

                for(int y = 0; y < FFTHeight; y++)
                {
                    int rowOffset   = y * byteCountPerPoint;
                    int valueOffset = firstDataByte + columnOffset + rowOffset;

                    double value = BitConverter.ToDouble(byteArray, valueOffset);

                    FFTList[x][y] = value;
                }
            }
        }

        #endregion
        #region 저장하기 - Save(filePath)

        /// <summary>
        /// 저장하기
        /// </summary>
        /// <param name="filePath">파일 경로</param>
        public void Save(string filePath)
        {
            FilePath = Path.GetFullPath(filePath);

            byte[] headerArray = new byte[256];

            headerArray[0] = 211;
            headerArray[1] = (byte)'S';
            headerArray[2] = (byte)'F';
            headerArray[3] = (byte)'F';
            headerArray[4] = (byte)'\r';
            headerArray[5] = (byte)'\n';
            headerArray[6] = (byte)' ';
            headerArray[7] = (byte)'\n';

            int magicNumber = BitConverter.ToInt32(headerArray, 0);

            if(magicNumber != 1179014099)
            {
                throw new InvalidDataException("magic number for SFF files is 1179014099");
            }

            string fileInfo = $"SFF {MajorVersion}.{MinorVersion}\r\n";

            byte[] fileInfoByteArray = Encoding.UTF8.GetBytes(fileInfo);

            if(fileInfoByteArray.Length > 32)
            {
                throw new InvalidDataException("file info cannot exceed 32 bytes");
            }

            Array.Copy(fileInfoByteArray, 0, headerArray, 8, fileInfoByteArray.Length);

            headerArray[40] = MajorVersion;
            headerArray[41] = MinorVersion;

            Array.Copy(BitConverter.GetBytes(SampleRate), 0, headerArray, 42, 4);
            Array.Copy(BitConverter.GetBytes(StepSize  ), 0, headerArray, 46, 4);
            Array.Copy(BitConverter.GetBytes(Width     ), 0, headerArray, 50, 4);

            Array.Copy(BitConverter.GetBytes(FFTSize      ), 0, headerArray, 54, 4);
            Array.Copy(BitConverter.GetBytes(FFTFirstIndex), 0, headerArray, 58, 4);
            Array.Copy(BitConverter.GetBytes(Height       ), 0, headerArray, 62, 4);
            Array.Copy(BitConverter.GetBytes(HZOffset     ), 0, headerArray, 66, 4);

            byte valueCountPerPoint = 1;
            byte byteCountPerValue  = 8;
            byte decibelUnitCount   = 0;
            byte dataExtraByte      = 0;

            headerArray[70] = valueCountPerPoint;
            headerArray[71] = byteCountPerValue;
            headerArray[72] = decibelUnitCount;
            headerArray[73] = dataExtraByte;

            Array.Copy(BitConverter.GetBytes(MELBINCount), 0, headerArray, 84, 4);
            Array.Copy(BitConverter.GetBytes(ImageHeight), 0, headerArray, 88, 4);
            Array.Copy(BitConverter.GetBytes(ImageWidth ), 0, headerArray, 92, 4);

            int firstDataByte = headerArray.Length;

            Array.Copy(BitConverter.GetBytes(firstDataByte), 0, headerArray, 80, 4);

            int dataPointCount    = ImageHeight * ImageWidth;
            int byteCountPerPoint = byteCountPerValue * valueCountPerPoint;

            byte[] fileByteArray = new byte[headerArray.Length + dataPointCount * byteCountPerPoint];

            Array.Copy(headerArray, 0, fileByteArray, 0, headerArray.Length);

            int byteCountPerColumn = ImageHeight * byteCountPerPoint;

            for(int x = 0; x < ImageWidth; x++)
            {
                int columnOffset = byteCountPerColumn * x;

                for(int y = 0; y < ImageHeight; y++)
                {
                    int rowOffset   = y * byteCountPerPoint;
                    int valueOffset = firstDataByte + columnOffset + rowOffset;

                    double value = FFTList[x][y];

                    Array.Copy(BitConverter.GetBytes(value), 0, fileByteArray, valueOffset, 8);
                }
            }

            File.WriteAllBytes(filePath, fileByteArray);
        }

        #endregion
        #region 픽셀 정보 구하기 - GetPixelInformation(x, y)

        /// <summary>
        /// 픽셀 정보 구하기
        /// </summary>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <returns>(시간(단위 : 초), 주파수(단위 : HZ), 진도) 튜플</returns>
        public (double timeSecond, double frequencyHZ, double magnitude) GetPixelInformation(int x, int y)
        {
            double timeSecond = (double)x * StepSize / SampleRate;

            double maximumFrequency = SampleRate / 2;
            double maximumMEL       = Transform.MelFromFreq(maximumFrequency);
            double fraction         = (ImageHeight - y) / (double)ImageHeight;
            double frequency        = IsMEL ? Transform.MelToFreq(fraction * maximumMEL) : fraction * maximumFrequency;

            double magnitude = double.NaN;

            try
            {
                magnitude = FFTList[x][ImageHeight - y - 1];
            }
            catch
            {
            }

            return (timeSecond, frequency, magnitude);
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 시간 계산하기 - CalculateTimes()

        /// <summary>
        /// 시간 계산하기
        /// </summary>
        private void CalculateTimes()
        {
            TimeArray = new double[ImageWidth];

            double step = (double)StepSize / SampleRate;

            for(int i = 0; i < ImageWidth; i++)
            {
                TimeArray[i] = i * step;
            }
        }

        #endregion
        #region 주파수 계산하기 - CalculateFrequencies()

        /// <summary>
        /// 주파수 계산하기
        /// </summary>
        private void CalculateFrequencies()
        {
            FrequencyArray = new double[ImageHeight];
            MELArray       = new double[ImageHeight];

            double maximumFrequency = SampleRate / 2;
            double maximumMEL       = Transform.MelFromFreq(maximumFrequency);

            for(int y = 0; y < ImageHeight; y++)
            {
                double fraction = (ImageHeight - y) / (double)ImageHeight;

                if(IsMEL)
                {
                    MELArray[y]       = fraction * maximumMEL;
                    FrequencyArray[y] = Transform.MelToFreq(MELArray[y]);
                }
                else
                {
                    FrequencyArray[y] = fraction * maximumFrequency;
                    MELArray[y]       = Transform.MelFromFreq(FrequencyArray[y]);
                }
            }
        }

        #endregion
    }
}

 

▶ SpectrogramGenerator.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading.Tasks;

using FftSharp;

namespace TestLibrary
{
    /// <summary>
    /// 스펙트로그램 제너레이터
    /// </summary>
    public class SpectrogramGenerator
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 색상 맵
        /// </summary>
        public ColorMap Colormap = ColorMap.Viridis;

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 설정
        /// </summary>
        private readonly Setting Setting;

        /// <summary>
        /// FFT 배열 리스트
        /// </summary>
        private readonly List<double[]> FFTArrayList = new List<double[]>();

        /// <summary>
        /// 미처리 FFT 리스트
        /// </summary>
        private readonly List<double> UnprocessedFFTList;

        /// <summary>
        /// 롤 오프셋
        /// </summary>
        private int rollOffset = 0;

        /// <summary>
        /// 고정 너비
        /// </summary>
        private int fixedWidth = 0;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 너비 - Width

        /// <summary>
        /// 너비
        /// </summary>
        public int Width { get => FFTArrayList.Count; }

        #endregion
        #region 높이 - Height

        /// <summary>
        /// 높이
        /// </summary>
        public int Height { get => Setting.Height; }

        #endregion
        #region FFT 크기 - FFTSize

        /// <summary>
        /// FFT 크기
        /// </summary>
        public int FFTSize { get => Setting.FFTSize; }

        #endregion
        #region 픽셀당 HZ - HZPerPixel

        /// <summary>
        /// 픽셀당 HZ
        /// </summary>
        public double HZPerPixel { get => Setting.HZPerPixel; }

        #endregion
        #region 픽셀당 초 카운트 - SecondCountPerPixel

        /// <summary>
        /// 픽셀당 초 카운트
        /// </summary>
        public double SecondCountPerPixel { get => Setting.StepLength; }

        #endregion
        #region 처리할 FFT 카운트 - FFTCountToProcess

        /// <summary>
        /// 처리할 FFT 카운트
        /// </summary>
        public int FFTCountToProcess { get => (UnprocessedFFTList.Count - Setting.FFTSize) / Setting.StepSize; }

        #endregion
        #region 처리 완료 FFT 카운트 - FFTCountProcessed

        /// <summary>
        /// 처리 완료 FFT 카운트
        /// </summary>
        public int FFTCountProcessed { get; private set; }

        #endregion
        #region 다음 컬럼 인덱스 - NextColumnIndex

        /// <summary>
        /// 다음 컬럼 인덱스
        /// </summary>
        public int NextColumnIndex { get => (FFTCountProcessed + rollOffset) % Width; }

        #endregion
        #region HZ 오프셋 - HZOffset

        /// <summary>
        /// HZ 오프셋
        /// </summary>
        public int HZOffset
        {
            get => Setting.HZOffset;
            set
            {
                Setting.HZOffset = value;
            }
        }

        #endregion
        #region 샘플 비율 - SampleRate

        /// <summary>
        /// 샘플 비율
        /// </summary>
        public int SampleRate { get => Setting.SampleRate; }

        #endregion
        #region 단계 크기 - StepSize

        /// <summary>
        /// 단계 크기
        /// </summary>
        public int StepSize { get => Setting.StepSize; }

        #endregion
        #region 최대 주파수 - MaximumFrequency

        /// <summary>
        /// 최대 주파수
        /// </summary>
        public double MaximumFrequency { get => Setting.MaximumFrequency; }

        #endregion
        #region 최소 주파수 - MinimumFrequency

        /// <summary>
        /// 최소 주파수
        /// </summary>
        public double MinimumFrequency { get => Setting.MinimumFrequency; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - SpectrogramGenerator(sampleRate, fftSize, stepSize, minimumFrequency, maximumFrequency, fixedWidth, hzOffset, initialAudioList)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="sampleRate">샘플 비율</param>
        /// <param name="fftSize">FFT 크기</param>
        /// <param name="stepSize">단계 크기</param>
        /// <param name="minimumFrequency">최소 주파수</param>
        /// <param name="maximumFrequency">최대 주파수</param>
        /// <param name="fixedWidth">고정 너비</param>
        /// <param name="hzOffset">HZ 오프셋</param>
        /// <param name="initialAudioList">초기 오디오 리스트</param>
        public SpectrogramGenerator
        (
            int          sampleRate,
            int          fftSize,
            int          stepSize,
            double       minimumFrequency = 0,
            double       maximumFrequency = double.PositiveInfinity,
            int?         fixedWidth       = null,
            int          hzOffset         = 0,
            List<double> initialAudioList = null
        )
        {
            Setting = new Setting(sampleRate, fftSize, stepSize, minimumFrequency, maximumFrequency, hzOffset);

            UnprocessedFFTList = initialAudioList ?? new List<double>();

            if(fixedWidth.HasValue)
            {
                SetFixedWidth(fixedWidth.Value);
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 문자열 구하기 - ToString()

        /// <summary>
        /// 문자열 구하기
        /// </summary>
        /// <returns>문자열</returns>
        public override string ToString()
        {
            double processedSampleCount = FFTArrayList.Count * Setting.StepSize + Setting.FFTSize;
            double processedSecondCount = processedSampleCount / Setting.SampleRate;
            string processedTime        = (processedSecondCount < 60) ? $"{processedSecondCount:N2} 초" : $"{processedSecondCount / 60.0:N2} 분";

            return $"스펙트로그램 ({Width}, {Height})" +
                   $"\n  수직 ({Height} 픽셀) : {Setting.MinimumFrequency:N0} - {Setting.MaximumFrequency:N0} HZ, " +
                   $"FFT 크기 : {Setting.FFTSize:N0} 샘플 수, {Setting.HZPerPixel:N2} HZ/픽셀" +
                   $"\n  수평 ({Width} 픽셀) : {processedTime}, 윈도우 : {Setting.FFTLength:N2} 초, " +
                   $"단계 : {Setting.StepLength:N2} 초, 오버랩 : {Setting.StepOverlapFraction * 100:N0}%";
        }

        #endregion
        #region 윈도우 설정하기 - SetWindow(newWindowArray)

        /// <summary>
        /// 윈도우 설정하기
        /// </summary>
        /// <param name="newWindowArray">신규 윈도우 배열</param>
        public void SetWindow(double[] newWindowArray)
        {
            if(newWindowArray.Length > Setting.FFTSize)
            {
                throw new ArgumentException("window length cannot exceed FFT size");
            }

            for(int i = 0; i < Setting.FFTSize; i++)
            {
                Setting.WindowArray[i] = 0;
            }

            int offset = (Setting.FFTSize - newWindowArray.Length) / 2;

            Array.Copy(newWindowArray, 0, Setting.WindowArray, offset, newWindowArray.Length);
        }

        #endregion
        #region 추가하기 - Add(audioEnumerable, process)

        /// <summary>
        /// 추가하기
        /// </summary>
        /// <param name="audioEnumerable">오디오 열거 가능형</param>
        /// <param name="process">처리 여부</param>
        public void Add(IEnumerable<double> audioEnumerable, bool process = true)
        {
            UnprocessedFFTList.AddRange(audioEnumerable);

            if(process)
            {
                Process();
            }
        }

        #endregion
        #region 롤 리셋하기 - ResetRoll(offset)

        /// <summary>
        /// 롤 리셋하기
        /// </summary>
        /// <param name="offset">오프셋</param>
        public void ResetRoll(int offset = 0)
        {
            this.rollOffset = -FFTCountProcessed + offset;
        }

        #endregion
        #region 처리하기 - Process()

        /// <summary>
        /// 처리하기
        /// </summary>
        /// <returns>처리 결과</returns>
        public double[][] Process()
        {
            if(FFTCountToProcess < 1)
            {
                return null;
            }

            int newFFTCount = FFTCountToProcess;

            double[][] newFFTArray = new double[newFFTCount][];

            Parallel.For
            (
                0,
                newFFTCount,
                newFFTIndex =>
                {
                    Complex[] bufferArray = new Complex[Setting.FFTSize];

                    int sourceIndex = newFFTIndex * Setting.StepSize;

                    for(int i = 0; i < Setting.FFTSize; i++)
                    {
                        bufferArray[i].Real = UnprocessedFFTList[sourceIndex + i] * Setting.WindowArray[i];
                    }

                    Transform.FFT(bufferArray);

                    newFFTArray[newFFTIndex] = new double[Setting.Height];

                    for(int i = 0; i < Setting.Height; i++)
                    {
                        newFFTArray[newFFTIndex][i] = bufferArray[Setting.FFTIndex1 + i].Magnitude / Setting.FFTSize;
                    }
                }
            );

            foreach(var newFFT in newFFTArray)
            {
                FFTArrayList.Add(newFFT);
            }

            FFTCountProcessed += newFFTArray.Length;

            UnprocessedFFTList.RemoveRange(0, newFFTCount * Setting.StepSize);

            PadOrTrimForFixedWidth();

            return newFFTArray;
        }

        #endregion
        #region MEL FFT 배열 리스트 구하기 - GetMELFFTArrayList(melBINCount)

        /// <summary>
        /// MEL FFT 배열 리스트 구하기
        /// </summary>
        /// <param name="melBINCount">MEL BIN 카운트</param>
        /// <returns>MEL FFT 배열 리스트</returns>
        public List<double[]> GetMELFFTArrayList(int melBINCount)
        {
            if(Setting.MinimumFrequency != 0)
            {
                throw new InvalidOperationException("cannot get Mel spectrogram unless minimum frequency is 0Hz");
            }

            List<double[]> melFFTArrayList = new List<double[]>();

            foreach(double[] fftArray in FFTArrayList)
            {
                melFFTArrayList.Add(Transform.MelScale(fftArray, SampleRate, melBINCount));
            }

            return melFFTArrayList;
        }

        #endregion
        #region 비트맵 구하기 - GetBitmap(intensity, db, dbScale, roll)

        /// <summary>
        /// 비트맵 구하기
        /// </summary>
        /// <param name="intensity">강도</param>
        /// <param name="db">데시빌 여부</param>
        /// <param name="dbScale">데시빌 스케일</param>
        /// <param name="roll">롤 여부</param>
        /// <returns>비트맵</returns>
        public Bitmap GetBitmap(double intensity = 1, bool db = false, double dbScale = 1, bool roll = false) =>
            BitmapHelper.GetBitmap(FFTArrayList, Colormap, intensity, db, dbScale, roll, NextColumnIndex);

        #endregion
        #region MEL 비트맵 구하기 - GetMELBitmap(melBINCount, intensity, db, dbScale, roll)

        /// <summary>
        /// MEL 비트맵 구하기
        /// </summary>
        /// <param name="melBINCount">MEL BIN 카운트</param>
        /// <param name="intensity">강도</param>
        /// <param name="db">데시빌 여부</param>
        /// <param name="dbScale">데시빌 스케일</param>
        /// <param name="roll">롤 여부</param>
        /// <returns>MEL 비트맵</returns>
        public Bitmap GetMELBitmap(int melBINCount = 25, double intensity = 1, bool db = false, double dbScale = 1, bool roll = false) =>
            BitmapHelper.GetBitmap(GetMELFFTArrayList(melBINCount), Colormap, intensity, db, dbScale, roll, NextColumnIndex);

        #endregion
        #region 이미지 저장하기 - SaveImage(filePath, intensity, db, dbScale, roll)

        /// <summary>
        /// 이미지 저장하기
        /// </summary>
        /// <param name="filePath">파일 경로</param>
        /// <param name="intensity">강도</param>
        /// <param name="db">데시빌 여부</param>
        /// <param name="dbScale">데시빌 스케일</param>
        /// <param name="roll">롤 여부</param>
        public void SaveImage(string filePath, double intensity = 1, bool db = false, double dbScale = 1, bool roll = false)
        {
            if(FFTArrayList.Count == 0)
            {
                throw new InvalidOperationException("Spectrogram contains no data. Use Add() to add signal data.");
            }

            string extension = Path.GetExtension(filePath).ToLower();

            ImageFormat imageFormat;

            if(extension == ".bmp")
            {
                imageFormat = ImageFormat.Bmp;
            }
            else if(extension == ".png")
            {
                imageFormat = ImageFormat.Png;
            }
            else if(extension == ".jpg" || extension == ".jpeg")
            {
                imageFormat = ImageFormat.Jpeg;
            }
            else if(extension == ".gif")
            {
                imageFormat = ImageFormat.Gif;
            }
            else
            {
                throw new ArgumentException("unknown file extension");
            }

            BitmapHelper.GetBitmap(FFTArrayList, Colormap, intensity, db, dbScale, roll, NextColumnIndex).Save(filePath, imageFormat);
        }

        #endregion
        #region 최대 비트맵 구하기 - GetMaximumBitmap(intensity, db, dbScale, roll, reduction)

        /// <summary>
        /// 최대 비트맵 구하기
        /// </summary>
        /// <param name="intensity">강도</param>
        /// <param name="db">데시빌 여부</param>
        /// <param name="dbScale">데시빌 스케일</param>
        /// <param name="roll">롤 여부</param>
        /// <param name="reduction">축소</param>
        /// <returns>최대 비트맵</returns>
        public Bitmap GetMaximumBitmap(double intensity = 1, bool db = false, double dbScale = 1, bool roll = false, int reduction = 4)
        {
            List<double[]> fftArrayList = new List<double[]>();

            for(int i = 0; i < FFTArrayList.Count; i++)
            {
                double[] valueArray1 = FFTArrayList[i];
                double[] valueArray2 = new double[valueArray1.Length / reduction];

                for(int j = 0; j < valueArray2.Length; j++)
                {
                    for(int k = 0; k < reduction; k++)
                    {
                        valueArray2[j] = Math.Max(valueArray2[j], valueArray1[j * reduction + k]);
                    }
                }

                fftArrayList.Add(valueArray2);
            }

            return BitmapHelper.GetBitmap(fftArrayList, Colormap, intensity, db, dbScale, roll, NextColumnIndex);
        }

        #endregion
        #region 데이터 저장하기 - SaveData(filePath, melBINCount)

        /// <summary>
        /// 데이터 저장하기
        /// </summary>
        /// <param name="filePath">파일 경로</param>
        /// <param name="melBINCount">MEL BIN 카운트</param>
        public void SaveData(string filePath, int melBINCount = 0)
        {
            if(!filePath.EndsWith(".sff", StringComparison.OrdinalIgnoreCase))
            {
                filePath += ".sff";
            }

            new SpectrogramFileFormat(this, melBINCount).Save(filePath);
        }

        #endregion
        #region 고정 너비 설정하기 - SetFixedWidth(width)

        /// <summary>
        /// 고정 너비 설정하기
        /// </summary>
        /// <param name="width">너비</param>
        public void SetFixedWidth(int width)
        {
            this.fixedWidth = width;

            PadOrTrimForFixedWidth();
        }

        #endregion
        #region 수직 스케일 비트맵 구하기 - GetVerticalScaleBitmap(width, hzOffset, tickSize, reduction)

        /// <summary>
        /// 수직 스케일 비트맵 구하기
        /// </summary>
        /// <param name="width">너비</param>
        /// <param name="hzOffset">HZ 오프셋</param>
        /// <param name="tickSize">틱 크기</param>
        /// <param name="reduction">축소</param>
        /// <returns>수직 스케일 비트맵</returns>
        public Bitmap GetVerticalScaleBitmap(int width, int hzOffset = 0, int tickSize = 3, int reduction = 1)
        {
            return BitmapHelper.GetVerticalScaleBitmap(width, Setting, hzOffset, tickSize, reduction);
        }

        #endregion
        #region 픽셀 Y 구하기 - GetPixelY(frequency, reduction)

        /// <summary>
        /// 픽셀 Y 구하기
        /// </summary>
        /// <param name="frequency">주파수</param>
        /// <param name="reduction">축소</param>
        /// <returns>픽셀 Y</returns>
        public int GetPixelY(double frequency, int reduction = 1)
        {
            int pixelCountFromZeroHZ           = (int)(Setting.PixelPerHZ * frequency / reduction);
            int pixelCountFromMinimumFrequency = pixelCountFromZeroHZ - Setting.FFTIndex1 / reduction + 1;
            int pixelRow                       = Setting.Height / reduction - 1 - pixelCountFromMinimumFrequency;

            return pixelRow - 1;
        }

        #endregion
        #region FFT 배열 리스트 구하기 - GetFFTArrayList()

        /// <summary>
        /// FFT 배열 리스트 구하기
        /// </summary>
        /// <returns>FFT 배열 리스트</returns>
        public List<double[]> GetFFTArrayList()
        {
            return FFTArrayList;
        }

        #endregion
        #region 피크 구하기 - GetPeak(latestFFT)

        /// <summary>
        /// 피크 구하기
        /// </summary>
        /// <param name="latestFFT">최신 FFT</param>
        /// <returns>(주파수 (단위 : HZ), 진도) 튜플</returns>
        public (double frequencyHZ, double magnitude) GetPeak(bool latestFFT = true)
        {
            if(FFTArrayList.Count == 0)
            {
                return (double.NaN, double.NaN);
            }

            if(latestFFT == false)
            {
                throw new NotImplementedException("peak of mean of all FFTs not yet supported");
            }

            double[] frequencyList = FFTArrayList[FFTArrayList.Count - 1];

            int peakIndex = 0;

            double peakMagnitude = 0;

            for(int i = 0; i < frequencyList.Length; i++)
            {
                if(frequencyList[i] > peakMagnitude)
                {
                    peakMagnitude = frequencyList[i];

                    peakIndex = i;
                }
            }

            double maximumFrequency      = SampleRate / 2;
            double peakFrequencyFraction = peakIndex / (double)frequencyList.Length;
            double peakFrequencyHZ       = maximumFrequency * peakFrequencyFraction;

            return (peakFrequencyHZ, peakMagnitude);
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 고정 너비용 제거 또는 채우기 - PadOrTrimForFixedWidth()

        /// <summary>
        /// 고정 너비용 제거 또는 채우기
        /// </summary>
        private void PadOrTrimForFixedWidth()
        {
            if(fixedWidth > 0)
            {
                int overhang = Width - fixedWidth;

                if(overhang > 0)
                {
                    FFTArrayList.RemoveRange(0, overhang);
                }

                while(FFTArrayList.Count < fixedWidth)
                {
                    FFTArrayList.Insert(0, new double[Height]);
                }
            }
        }

        #endregion
    }
}

 

▶ Tool.cs

using System;
using System.Linq;

namespace TestLibrary
{
    /// <summary>
    /// 도구
    /// </summary>
    public static class Tool
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 평균 FFT 배열 구하기 - GetMeanFFTArray(sff, db)

        /// <summary>
        /// 평균 FFT 배열 구하기
        /// </summary>
        /// <param name="sff">스펙트로그램 파일 포맷</param>
        /// <param name="db">데시빌 여부</param>
        /// <returns>평균 FFT 배열</returns>
        public static double[] GetMeanFFTArray(SpectrogramFileFormat sff, bool db = false)
        {
            double[] meanArray = new double[sff.FFTList[0].Length];

            foreach(double[] fftArray in sff.FFTList)
            {
                for(int y = 0; y < fftArray.Length; y++)
                {
                    meanArray[y] += fftArray[y];
                }
            }

            for(int i = 0; i < meanArray.Length; i++)
            {
                meanArray[i] /= sff.FFTList.Count();
            }

            if(db)
            {
                for(int i = 0; i < meanArray.Length; i++)
                {
                    meanArray[i] = 20 * Math.Log10(meanArray[i]);
                }
            }

            if(meanArray[meanArray.Length - 1] <= 0)
            {
                meanArray[meanArray.Length - 1] = meanArray[meanArray.Length - 2];
            }

            return meanArray;
        }

        #endregion
        #region 평균 전력 배열 구하기 - GetMeanPower(sff, db)

        /// <summary>
        /// 평균 전력 배열 구하기
        /// </summary>
        /// <param name="sff">스펙트로그램 파일 포맷</param>
        /// <param name="db">데시빌 여부</param>
        /// <returns>평균 전력 배열</returns>
        public static double[] GetMeanPower(SpectrogramFileFormat sff, bool db = false)
        {
            double[] powerArray = new double[sff.FFTList.Count];

            for(int i = 0; i < sff.FFTList.Count; i++)
            {
                powerArray[i] = (double)sff.FFTList[i].Sum() / sff.FFTList[i].Length;
            }

            if(db)
            {
                for(int i = 0; i < powerArray.Length; i++)
                {
                    powerArray[i] = 20 * Math.Log10(powerArray[i]);
                }
            }

            return powerArray;
        }

        #endregion
        #region 정점 주파수 구하기 - GetPeakFrequency(sff, firstFFTOnly)

        /// <summary>
        /// 정점 주파수 구하기
        /// </summary>
        /// <param name="sff">스펙트로그램 파일 포맷</param>
        /// <param name="firstFFTOnly">첫번째 FFT만 여부</param>
        /// <returns>정점 주파수</returns>
        public static double GetPeakFrequency(SpectrogramFileFormat sff, bool firstFFTOnly = false)
        {
            double[] frequencyArray = firstFFTOnly ? sff.FFTList[0] : GetMeanFFTArray(sff, false);

            int    peakIndex = 0;
            double peakPower = 0;

            for(int i = 0; i < frequencyArray.Length; i++)
            {
                if(frequencyArray[i] > peakPower)
                {
                    peakPower = frequencyArray[i];
                    peakIndex = i;
                }
            }

            double maximumFrequency = sff.SampleRate / 2;
            double fraction         = peakIndex / (double)sff.ImageHeight;

            if(sff.MELBINCount > 0)
            {
                double maximumMEL = FftSharp.Transform.MelFromFreq(maximumFrequency);

                return FftSharp.Transform.MelToFreq(fraction * maximumMEL);
            }
            else
            {
                return fraction * maximumFrequency;
            }
        }

        #endregion
        #region 피아노 키 구하기 - GetPianoKey(frequency)

        /// <summary>
        /// 피아노 키 구하기
        /// </summary>
        /// <param name="frequency">주파수 (단위 : HZ)</param>
        /// <returns>피아노 키</returns>
        public static int GetPianoKey(double frequency)
        {
            double pianoKey = (39.86 * Math.Log10(frequency / 440)) + 49;

            return (int)Math.Round(pianoKey);
        }

        #endregion
        #region 미디 노트 구하기 - GetMIDINote(frequency)

        /// <summary>
        /// 미디 노트 구하기
        /// </summary>
        /// <param name="frequency">주파수 (단위 : HZ)</param>
        /// <returns>미디 노트</returns>
        public static int GetMIDINote(double frequency)
        {
            return GetPianoKey(frequency) + 20;
        }

        #endregion
    }
}

 

▶ Listener.cs

using System;
using System.Collections.Generic;

using NAudio.Wave;

namespace TestProject
{
    /// <summary>
    /// 리스너
    /// </summary>
    public class Listener : IDisposable
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 샘플 비율
        /// </summary>
        public readonly int SampleRate;

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 웨이브 입력 이벤트
        /// </summary>
        private readonly WaveInEvent waveInEvent;

        /// <summary>
        /// 오디오 리스트
        /// </summary>
        private readonly List<double> audioList = new List<double>();

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 진폭 분수 - AmplitudeFraction

        /// <summary>
        /// 진폭 분수
        /// </summary>
        public double AmplitudeFraction { get; private set; }

        #endregion
        #region 전체 샘플 카운트 - TotalSampleCount

        /// <summary>
        /// 전체 샘플 카운트
        /// </summary>
        public double TotalSampleCount { get; private set; }

        #endregion
        #region 전체 시간 (단위 : 초) - TotalTime

        /// <summary>
        /// 전체 시간 (단위 : 초)
        /// </summary>
        public double TotalTime
        {
            get
            {
                return (double)TotalSampleCount / SampleRate;
            }
        }

        #endregion
        #region 메모리 내 샘플 카운트 - SampleCountInMemory

        /// <summary>
        /// 메모리 내 샘플 카운트
        /// </summary>
        public int SampleCountInMemory
        {
            get
            {
                return this.audioList.Count;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - Listener(deviceIndex, sampleRate)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="deviceIndex">장치 인덱스</param>
        /// <param name="sampleRate">샘플 비율</param>
        public Listener(int deviceIndex, int sampleRate)
        {
            SampleRate = sampleRate;

            this.waveInEvent = new WaveInEvent
            {
                DeviceNumber       = deviceIndex,
                WaveFormat         = new WaveFormat(sampleRate, bits : 16, channels : 1),
                BufferMilliseconds = 20
            };

            this.waveInEvent.DataAvailable += waveInEvent_DataAvailable;

            this.waveInEvent.StartRecording();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 신규 오디오 배열 구하기 - GetNewAudioArray()

        /// <summary>
        /// 신규 오디오 배열 구하기
        /// </summary>
        /// <returns>신규 오디오 배열</returns>
        public double[] GetNewAudioArray()
        {
            lock(this.audioList)
            {
                double[] valueArray = new double[this.audioList.Count];

                for(int i = 0; i < valueArray.Length; i++)
                {
                    valueArray[i] = this.audioList[i];
                }

                this.audioList.RemoveRange(0, valueArray.Length);

                return valueArray;
            }
        }

        #endregion
        #region 리소스 해제하기 - Dispose()

        /// <summary>
        /// 리소스 해제하기
        /// </summary>
        public void Dispose()
        {
            this.waveInEvent?.StopRecording();

            this.waveInEvent?.Dispose();
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 웨이브 입력 이벤트 데이터 이용 가능시 처리하기 - waveInEvent_DataAvailable(sender, e)

        /// <summary>
        /// 웨이브 입력 이벤트 데이터 이용 가능시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void waveInEvent_DataAvailable(object sender, WaveInEventArgs e)
        {
            int      byteCountPerSample = this.waveInEvent.WaveFormat.BitsPerSample / 8;
            int      newSampleCount     = e.BytesRecorded / byteCountPerSample;
            double[] bufferArray        = new double[newSampleCount];
            double   peak               = 0d;

            for(int i = 0; i < newSampleCount; i++)
            {
                bufferArray[i] = BitConverter.ToInt16(e.Buffer, i * byteCountPerSample);

                peak = Math.Max(peak, bufferArray[i]);
            }

            lock(this.audioList)
            {
                this.audioList.AddRange(bufferArray);
            }

            AmplitudeFraction = peak / (1 << 15);

            TotalSampleCount += newSampleCount;
        }

        #endregion
    }
}

 

▶ MainForm.cs

using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;

using NAudio.Wave;

using TestLibrary;

namespace TestProject
{
    /// <summary>
    /// 메인 폼
    /// </summary>
    public partial class MainForm : Form
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 스펙트로그램 제너레이터
        /// </summary>
        private SpectrogramGenerator generator;

        /// <summary>
        /// 리스너
        /// </summary>
        private Listener listener;

        /// <summary>
        /// 색상 맵 배열
        /// </summary>
        private ColorMap[] colorMapArray;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - MainForm()

        /// <summary>
        /// 생성자
        /// </summary>
        public MainForm()
        {
            InitializeComponent();

            if(WaveIn.DeviceCount == 0)
            {
                MessageBox.Show
                (
                    "No audio input devices found.\n\nThis program will now exit.",
                    "ERROR",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error
                );

                Close();
            }

            this.deviceComboBox.Items.Clear();

            for(int i = 0; i < WaveIn.DeviceCount; i++)
            {
                this.deviceComboBox.Items.Add(WaveIn.GetCapabilities(i).ProductName);
            }

            for(int i = 9; i < 16; i++)
            {
                this.fftSizeComboBox.Items.Add($"2^{i} ({1 << i:N0})");
            }

            this.colorMapArray = ColorMap.GetColorMapArray();

            foreach(ColorMap colorMap in this.colorMapArray)
            {
                this.colorMapComboBox.Items.Add(colorMap.Name);
            }

            this.deviceComboBox.SelectedIndexChanged   += deviceComboBox_SelectedIndexChanged;
            this.fftSizeComboBox.SelectedIndexChanged  += fftSizeComboBox_SelectedIndexChanged;
            this.colorMapComboBox.SelectedIndexChanged += colorMapComboBox_SelectedIndexChanged;
            this.rollCheckBox.CheckedChanged           += rollCheckBox_CheckedChanged;
            this.resetRollButton.Click                 += resetRollButton_Click;
            this.timer.Tick                            += timer_Tick;

            this.deviceComboBox.SelectedIndex   = 0;
            this.fftSizeComboBox.SelectedIndex  = 1;
            this.colorMapComboBox.SelectedIndex = this.colorMapComboBox.Items.IndexOf("LoporaColorMap");
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Private
        //////////////////////////////////////////////////////////////////////////////// Event

        #region 장치 콤보 박스 선택 인덱스 변경시 처리하기 - deviceComboBox_SelectedIndexChanged(sender, e)

        /// <summary>
        /// 장치 콤보 박스 선택 인덱스 변경시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void deviceComboBox_SelectedIndexChanged(object sender, EventArgs e) => StartListening();

        #endregion
        #region FFT 크기 콤보 박스 선택 인덱스 변경시 처리하기 - fftSizeComboBox_SelectedIndexChanged(sender, e)

        /// <summary>
        /// FFT 크기 콤보 박스 선택 인덱스 변경시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void fftSizeComboBox_SelectedIndexChanged(object sender, EventArgs e) => StartListening();

        #endregion
        #region 색상 맵 콤보 박스 선택 인덱스 변경시 처리하기 - colorMapComboBox_SelectedIndexChanged(sender, e)

        /// <summary>
        /// 색상 맵 콤보 박스 선택 인덱스 변경시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void colorMapComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.generator.Colormap = this.colorMapArray[this.colorMapComboBox.SelectedIndex];
        }

        #endregion
        #region Roll 체크 박스 체크 변경시 처리하기 - rollCheckBox_CheckedChanged(sender, e)

        /// <summary>
        /// Roll 체크 박스 체크 변경시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void rollCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            this.resetRollButton.Enabled = this.rollCheckBox.Checked;
        }

        #endregion
        #region Roll 리셋하기 버튼 클릭시 처리하기 - resetRollButton_Click(sender, e)

        /// <summary>
        /// Roll 리셋하기 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void resetRollButton_Click(object sender, EventArgs e)
        {
            this.generator.ResetRoll();
        }

        #endregion
        #region 타이머 틱 처리하기 - timer_Tick(sender, e)

        /// <summary>
        /// 타이머 틱 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void timer_Tick(object sender, EventArgs e)
        {
            double[] newAudioArray = this.listener.GetNewAudioArray();

            this.generator.Add(newAudioArray, process : false);

            double multiplier = this.brightnessTrackBar.Value / 20d;

            if(this.generator.FFTCountToProcess > 0)
            {
                Stopwatch stopWatch = Stopwatch.StartNew();

                this.generator.Process();

                this.generator.SetFixedWidth(spectorgramPictureBox.Width);

                Bitmap spectrogramBitmap = new Bitmap(this.generator.Width, this.generator.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);

                using(Bitmap indexedSpectrogramBitmap = this.generator.GetBitmap(multiplier, this.dbCheckBox.Checked, roll : this.rollCheckBox.Checked))
                using(Graphics graphics = Graphics.FromImage(spectrogramBitmap))
                using(Pen pen = new Pen(Color.White))
                {
                    graphics.DrawImage(indexedSpectrogramBitmap, 0, 0);

                    if(this.rollCheckBox.Checked)
                    {
                        graphics.DrawLine(pen, this.generator.NextColumnIndex, 0, this.generator.NextColumnIndex, this.spectorgramPictureBox.Height);
                    }
                }

                stopWatch.Stop();

                this.spectorgramPictureBox.Image?.Dispose();

                this.spectorgramPictureBox.Image = spectrogramBitmap;

                this.renderTimeStatusLabel.Text = $"렌러딩 시간 : {stopWatch.ElapsedMilliseconds:D2} 밀리초";

                this.peakFrequencyStatusLabel.Text = $"피크 (Hz): {this.generator.GetPeak().frequencyHZ:N0}";
            }

            this.timeStatusLabel.Text = $"시간 : {this.listener.TotalTime:N3} 초";

            this.fftCountProcessedStatusLabel.Text = $"처리 FFT 수 : {this.generator.FFTCountProcessed:N0}";

            this.amplitudeProgressBar.Value = (int)(this.listener.AmplitudeFraction * this.amplitudeProgressBar.Maximum);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Function

        #region 청취 시작하기 - StartListening()

        /// <summary>
        /// 청취 시작하기
        /// </summary>
        private void StartListening()
        {
            int sampleRate = 6000;
            int fftSize    = 1 << (9 + this.fftSizeComboBox.SelectedIndex);
            int stepSize   = fftSize / 20;

            this.spectorgramPictureBox.Image?.Dispose();

            this.spectorgramPictureBox.Image = null;

            this.listener?.Dispose();

            this.listener = new Listener(this.deviceComboBox.SelectedIndex, sampleRate);

            this.generator = new SpectrogramGenerator(sampleRate, fftSize, stepSize);

            this.spectorgramPictureBox.Height = this.generator.Height;

            this.verticalScalePictureBox.Image?.Dispose();

            this.verticalScalePictureBox.Image  = this.generator.GetVerticalScaleBitmap(this.verticalScalePictureBox.Width);
            this.verticalScalePictureBox.Height = this.generator.Height;
        }

        #endregion
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요