From 668bcd4d106952991c3826da09d1f8a772d177f7 Mon Sep 17 00:00:00 2001 From: rs <> Date: Mon, 19 Jan 2026 21:50:56 -0600 Subject: [PATCH] Add things for mp3 cassette project --- .gitignore | 5 +- libraries/dsp/pcm16_2ch_sum.vhd | 4 +- libraries/dsp/pcm16_2ch_tapeeffect.vhd | 228 ++++++++++++++++++++++ libraries/dsp/pcm16_2ch_vardelay.vhd | 146 ++++++++++++++ libraries/dsp/pcm16_2ch_windowsum.vhd | 96 ++++++++++ libraries/dsp/table_sine_1k_16.vhd | 105 ++++++++++ projects/mp3tape/nexys2.vhd | 237 +++++++++++++++++++++++ projects/mp3tape/test_tapeeffect.vhd | 72 +++++++ projects/mp3tape/tests/test_nexys2.vhd | 104 ++++++++++ projects/mp3tape/tools/fft.py | 253 +++++++++++++++++++++++++ projects/mp3tape/tools/jankman.py | 190 +++++++++++++++++++ 11 files changed, 1437 insertions(+), 3 deletions(-) create mode 100644 libraries/dsp/pcm16_2ch_tapeeffect.vhd create mode 100644 libraries/dsp/pcm16_2ch_vardelay.vhd create mode 100644 libraries/dsp/pcm16_2ch_windowsum.vhd create mode 100644 libraries/dsp/table_sine_1k_16.vhd create mode 100644 projects/mp3tape/nexys2.vhd create mode 100644 projects/mp3tape/test_tapeeffect.vhd create mode 100644 projects/mp3tape/tests/test_nexys2.vhd create mode 100755 projects/mp3tape/tools/fft.py create mode 100755 projects/mp3tape/tools/jankman.py diff --git a/.gitignore b/.gitignore index e01d0b5..09b942d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,10 @@ projects/nexys2_host_controller/host/jtag projects/nexys2_host_controller/host/stm projects/nexys2_host_controller/host/stm_test -# Test audio files +# Test audio files, because copyright law is important to me *.wav +*.mp3 +*.jnk .idea/ +__pycache__/ diff --git a/libraries/dsp/pcm16_2ch_sum.vhd b/libraries/dsp/pcm16_2ch_sum.vhd index 369f26a..2f4c89d 100644 --- a/libraries/dsp/pcm16_2ch_sum.vhd +++ b/libraries/dsp/pcm16_2ch_sum.vhd @@ -53,8 +53,8 @@ begin samp_b_l <= signed(b_dat_i(15 downto 0)); samp_b_r <= signed(b_dat_i(31 downto 16)); - result_l <= samp_a_l + samp_b_l; - result_r <= samp_a_r + samp_b_r; + result_l <= (samp_a_l(15) & samp_a_l) + ((samp_b_l(15)) & samp_b_l); + result_r <= (samp_a_r(15) & samp_a_r) + ((samp_b_r(15)) & samp_b_r); e_sat_l: entity work.saturate generic map (WIDTH_IN => 17, WIDTH_OUT => 16) diff --git a/libraries/dsp/pcm16_2ch_tapeeffect.vhd b/libraries/dsp/pcm16_2ch_tapeeffect.vhd new file mode 100644 index 0000000..1769458 --- /dev/null +++ b/libraries/dsp/pcm16_2ch_tapeeffect.vhd @@ -0,0 +1,228 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library work; + + +entity pcm16_2ch_tapeeffect is + port ( + rst_i: in std_logic; + clk_i: in std_logic; + + en_rolloff: in std_logic; + en_flutter: in std_logic; + en_wow: in std_logic; + en_noise: in std_logic; + + stb_i: in std_logic; + rdy_o: out std_logic; + dat_i: in std_logic_vector(31 downto 0); + + stb_o: out std_logic; + rdy_i: in std_logic; + dat_o: out std_logic_vector(31 downto 0) + ); +end pcm16_2ch_tapeeffect; + + +architecture behavioral of pcm16_2ch_tapeeffect is + + -- Stage 1 - High frequency rolloff + + signal split_a_stb: std_logic; + signal split_a_rdy: std_logic; + signal split_b_stb: std_logic; + signal split_b_rdy: std_logic; + signal split_dat: std_logic_vector(31 downto 0); + + signal filter_stb: std_logic; + signal filter_rdy: std_logic; + signal filter_dat: std_logic_vector(31 downto 0); + + signal bypass_stb: std_logic; + signal bypass_rdy: std_logic; + signal bypass_dat: std_logic_vector(31 downto 0); + + signal merge_stb: std_logic; + signal merge_rdy: std_logic; + signal merge_dat: std_logic_vector(31 downto 0); + + signal stage1_stb: std_logic; + signal stage1_rdy: std_logic; + signal stage1_dat: std_logic_vector(31 downto 0); + + -- Stage 2 - Wow and flutter + + constant FLUTTER_WIDTH: integer := 9; + constant FLUTTER_INC: std_logic_vector(15 downto 0) := "00000000"&"00100000"; + signal flutter_stb: std_logic; + signal flutter_rdy: std_logic; + signal flutter_dat: std_logic_vector(FLUTTER_WIDTH-1 downto 0); + + signal delay_dat: std_logic_vector(9 downto 0); + + signal stage2_stb: std_logic; + signal stage2_rdy: std_logic; + signal stage2_dat: std_logic_vector(31 downto 0); + + -- Stage 3 - Hiss + + signal noise_stb: std_logic; + signal noise_rdy: std_logic; + signal noise_raw: std_logic_vector( 9 downto 0); + signal noise_dat: std_logic_vector(31 downto 0); + +begin + + ---------------------------------------------------------------------------- + -- Low pass filter the audio stream + + e_split: entity work.pipectrl_split + generic map (WIDTH => 32) + port map ( + rst_i => rst_i, + clk_i => clk_i, + + stb_i => stb_i, + rdy_o => rdy_o, + dat_i => dat_i, + + a_stb_o => split_a_stb, + a_rdy_i => split_a_rdy, + + b_stb_o => split_b_stb, + b_rdy_i => split_b_rdy, + + dat_o => split_dat + ); + + e_filter: entity work.pcm16_2ch_windowsum + generic map (WINDOW => 16) + port map ( + rst_i => rst_i, + clk_i => clk_i, + + stb_i => split_a_stb, + rdy_o => split_a_rdy, + dat_i => split_dat, + + stb_o => filter_stb, + rdy_i => filter_rdy, + dat_o => filter_dat + ); + + e_bypass: entity work.pipectrl + generic map (WIDTH => 32) + port map ( + rst_i => rst_i, + clk_i => clk_i, + + stb_i => split_b_stb, + rdy_o => split_b_rdy, + dat_i => split_dat, + + stb_o => bypass_stb, + rdy_i => bypass_rdy, + dat_o => bypass_dat + ); + + e_filter_merge: entity work.merge + port map ( + a_stb_i => filter_stb, + a_rdy_o => filter_rdy, + + b_stb_i => bypass_stb, + b_rdy_o => bypass_rdy, + + stb_o => merge_stb, + rdy_i => merge_rdy + ); + merge_dat <= filter_dat when en_rolloff = '1' else bypass_dat; + + e_filter_ctrl: entity work.pipectrl + generic map (WIDTH => 32) + port map ( + rst_i => rst_i, + clk_i => clk_i, + + stb_i => merge_stb, + rdy_o => merge_rdy, + dat_i => merge_dat, + + stb_o => stage1_stb, + rdy_i => stage1_rdy, + dat_o => stage1_dat + ); + + ---------------------------------------------------------------------------- + -- Variable delay for wow and flutter + + e_flutter: entity work.src_fracstep + generic map (WIDTH_OUT => FLUTTER_WIDTH) + port map ( + rst_i => rst_i, + clk_i => clk_i, + + inc_i => std_logic_vector(unsigned(FLUTTER_INC)), + + stb_o => flutter_stb, + rdy_i => flutter_rdy, + dat_o => flutter_dat + ); + + delay_dat <= std_logic_vector(resize(unsigned(flutter_dat), 10)) when en_flutter = '1' else (others => '0'); + + e_vardelay: entity work.pcm16_2ch_vardelay + port map ( + rst_i => rst_i, + clk_i => clk_i, + + delay_stb_i => flutter_stb, + delay_rdy_o => flutter_rdy, + delay_dat_i => delay_dat, + + audio_stb_i => stage1_stb, + audio_rdy_o => stage1_rdy, + audio_dat_i => stage1_dat, + + stb_o => stage2_stb, + rdy_i => stage2_rdy, + dat_o => stage2_dat + ); + + ---------------------------------------------------------------------------- + -- Add noise stream to audio stream + + e_noise: entity work.src_noise + generic map (WIDTH => 10) + port map ( + clk_i => clk_i, + + stb_o => noise_stb, + rdy_i => noise_rdy, + dat_o => noise_raw + ); + + noise_dat <= std_logic_vector(resize(signed(noise_raw), 16)) & + std_logic_vector(resize(signed(noise_raw), 16)) when en_noise = '1' else (others => '0'); + + e_sum: entity work.pcm16_2ch_sum + port map ( + rst_i => rst_i, + clk_i => clk_i, + + a_stb_i => stage2_stb, + a_rdy_o => stage2_rdy, + a_dat_i => stage2_dat, + + b_stb_i => noise_stb, + b_rdy_o => noise_rdy, + b_dat_i => noise_dat, + + stb_o => stb_o, + rdy_i => rdy_i, + dat_o => dat_o + ); + +end behavioral; diff --git a/libraries/dsp/pcm16_2ch_vardelay.vhd b/libraries/dsp/pcm16_2ch_vardelay.vhd new file mode 100644 index 0000000..d79d347 --- /dev/null +++ b/libraries/dsp/pcm16_2ch_vardelay.vhd @@ -0,0 +1,146 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library unisim; +use unisim.vcomponents.all; + +library work; + + +entity pcm16_2ch_vardelay is + port ( + rst_i: in std_logic; + clk_i: in std_logic; + + delay_stb_i: in std_logic; + delay_rdy_o: out std_logic; + delay_dat_i: in std_logic_vector(9 downto 0); + + audio_stb_i: in std_logic; + audio_rdy_o: out std_logic; + audio_dat_i: in std_logic_vector(31 downto 0); + + stb_o: out std_logic; + rdy_i: in std_logic; + dat_o: out std_logic_vector(31 downto 0) + ); +end pcm16_2ch_vardelay; + + +architecture behavioral of pcm16_2ch_vardelay is + + constant COUNT_WIDTH: integer := 10; + + signal en: std_logic; + signal stb: std_logic; + signal rdy: std_logic; + + signal samp_l: std_logic_vector(15 downto 0); + signal samp_r: std_logic_vector(15 downto 0); + + signal result_l: std_logic_vector(15 downto 0); + signal result_r: std_logic_vector(15 downto 0); + + signal ptr_head_reg: unsigned(COUNT_WIDTH-1 downto 0) := (others => '0'); + signal ptr_tail: unsigned(COUNT_WIDTH-1 downto 0); + +begin + + e_merge: entity work.merge + port map ( + a_stb_i => delay_stb_i, + a_rdy_o => delay_rdy_o, + b_stb_i => audio_stb_i, + b_rdy_o => audio_rdy_o, + stb_o => stb, + rdy_i => rdy + ); + + e_ctrl: entity work.pipectrl + generic map (WIDTH => 0) + port map ( + rst_i => rst_i, + clk_i => clk_i, + en_o => en, + stb_i => stb, + rdy_o => rdy, + dat_i => open, + stb_o => stb_o, + rdy_i => rdy_i, + dat_o => open + ); + + process (rst_i, clk_i, en, ptr_head_reg) + begin + if rising_edge(clk_i) then + if rst_i = '1' then + -- Probably don't need reset logic + elsif en = '1' then + ptr_head_reg <= ptr_head_reg + 1; + end if; + end if; + end process; + + samp_l <= audio_dat_i(15 downto 0); + samp_r <= audio_dat_i(31 downto 16); + + ptr_tail <= (ptr_head_reg - unsigned(delay_dat_i)) - 1; + + e_fifo_l: ramb16_s18_s18 + port map ( + -- Head port (insertion) + WEA => '1', + ENA => en, + SSRA => '0', + CLKA => clk_i, + ADDRA => std_logic_vector(ptr_head_reg), + DIA => samp_l, + DIPA => (others => '0'), + + DOA => open, + DOPA => open, + + -- Tail port (removal) + WEB => '0', + ENB => en, + SSRB => '0', + CLKB => clk_i, + ADDRB => std_logic_vector(ptr_tail), + DIB => (others => '0'), + DIPB => (others => '0'), + + DOB => result_l, + DOPB => open + ); + + e_fifo_r: ramb16_s18_s18 + port map ( + -- Head port (insertion) + WEA => '1', + ENA => en, + SSRA => '0', + CLKA => clk_i, + ADDRA => std_logic_vector(ptr_head_reg), + DIA => samp_r, + DIPA => (others => '0'), + + DOA => open, + DOPA => open, + + -- Tail port (removal) + WEB => '0', + ENB => en, + SSRB => '0', + CLKB => clk_i, + ADDRB => std_logic_vector(ptr_tail), + DIB => (others => '0'), + DIPB => (others => '0'), + + DOB => result_r, + DOPB => open + ); + + dat_o <= result_r & result_l; + +end behavioral; diff --git a/libraries/dsp/pcm16_2ch_windowsum.vhd b/libraries/dsp/pcm16_2ch_windowsum.vhd new file mode 100644 index 0000000..83bff1c --- /dev/null +++ b/libraries/dsp/pcm16_2ch_windowsum.vhd @@ -0,0 +1,96 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.math_real.all; + +library work; + + +entity pcm16_2ch_windowsum is + generic ( + WINDOW: positive := 16 + ); + port ( + rst_i: in std_logic; + clk_i: in std_logic; + + stb_i: in std_logic; + rdy_o: out std_logic; + dat_i: in std_logic_vector(31 downto 0); + + stb_o: out std_logic; + rdy_i: in std_logic; + dat_o: out std_logic_vector(31 downto 0) + ); +end pcm16_2ch_windowsum; + + +architecture behavioral of pcm16_2ch_windowsum is + + constant FILT_WIDTH: positive := 16+integer(ceil(log2(real(WINDOW)))); + + signal en: std_logic; + + signal samp_l: std_logic_vector(15 downto 0); + signal samp_r: std_logic_vector(15 downto 0); + + signal filt_l: std_logic_vector(FILT_WIDTH-1 downto 0); + signal filt_r: std_logic_vector(FILT_WIDTH-1 downto 0); + + signal result_l: std_logic_vector(15 downto 0); + signal result_r: std_logic_vector(15 downto 0); + +begin + + e_ctrl: entity work.pipectrl + generic map (WIDTH => 0) + port map ( + rst_i => rst_i, + clk_i => clk_i, + en_o => en, + stb_i => stb_i, + rdy_o => rdy_o, + dat_i => open, + stb_o => stb_o, + rdy_i => rdy_i, + dat_o => open + ); + + samp_l <= dat_i(15 downto 0); + samp_r <= dat_i(31 downto 16); + + e_filter_l: entity work.filter_windowsum + generic map ( + WIDTH => 16, + WINDOW => WINDOW + ) + port map ( + rst_i => rst_i, + clk_i => clk_i, + + en_i => en, + + dat_i => samp_l, + dat_o => filt_l + ); + + e_filter_r: entity work.filter_windowsum + generic map ( + WIDTH => 16, + WINDOW => WINDOW + ) + port map ( + rst_i => rst_i, + clk_i => clk_i, + + en_i => en, + + dat_i => samp_r, + dat_o => filt_r + ); + + result_l <= filt_l(FILT_WIDTH-1 downto FILT_WIDTH-16); + result_r <= filt_r(FILT_WIDTH-1 downto FILT_WIDTH-16); + + dat_o <= result_r & result_l; + +end behavioral; diff --git a/libraries/dsp/table_sine_1k_16.vhd b/libraries/dsp/table_sine_1k_16.vhd new file mode 100644 index 0000000..7d2fef1 --- /dev/null +++ b/libraries/dsp/table_sine_1k_16.vhd @@ -0,0 +1,105 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; +use ieee.math_real.all; + +library unisim; +use unisim.vcomponents.all; + + +entity table_sine_1k_16 is + port ( + rst_i: in std_logic; + clk_i: in std_logic; + + en: in std_logic; + + adr_i: in std_logic_vector(9 downto 0); + dat_o: out std_logic_vector(15 downto 0) + ); +end table_sine_1k_16; + + +architecture behavioral of table_sine_1k_16 is +begin + + e_table: ramb16_s18 + generic map ( + INIT_00 = x"0bc30afb0a32096a08a107d907100647057e04b603ed0324025b019200c90000", + INIT_01 = x"1833176d16a715e1151b1455138e12c71200113910720fab0ee30e1b0d530c8b", + INIT_02 = x"246723a622e422232161209f1fdc1f191e561d931ccf1c0b1b461a8219bd18f8", + INIT_03 = x"30412f862ecc2e102d542c982bdb2b1e2a6129a328e52826276726a725e72527", + INIT_04 = x"3ba43af23a3f398c38d83824376f36b93603354d349633de3326326d31b430fb", + INIT_05 = x"467445cc4523447a43d04325427941cd412040733fc53f163e673db73d073c56", + INIT_06 = x"50974ffa4f5d4ebf4e204d804ce04c3f4b9d4afa4a5749b3490e486947c3471c", + INIT_07 = x"59f3596358d3584257b0571d568955f4555f54c95432539a5301526851ce5133", + INIT_08 = x"627161f0616e60eb60675fe25f5d5ed65e4f5dc65d3d5cb35c285b9c5b0f5a81", + INIT_09 = x"69fc698b691968a5683167bc674566ce665665dd656264e7646b63ee637062f1", + INIT_0a = x"708270226fc06f5e6efa6e956e306dc96d616cf86c8e6c236bb76b4a6adb6a6c", + INIT_0b = x"75f375a47554750374b1745e740a73b5735e730672ae725471f9719d714070e1", + INIT_0c = x"7a417a0479c779897949790878c67883783f77f977b3776b772276d8768d7640", + INIT_0d = x"7d617d387d0e7ce27cb67c887c597c297bf77bc47b917b5c7b257aee7ab57a7c", + INIT_0e = x"7f4c7f377f207f087eef7ed47eb97e9c7e7e7e5e7e3e7e1c7df97dd57db07d89", + INIT_0f = x"7ffe7ffc7ff97ff57fef7fe87fe07fd77fcd7fc17fb47fa67f967f867f747f61", + INIT_10 = x"7f747f867f967fa67fb47fc17fcd7fd77fe07fe87fef7ff57ff97ffc7ffe7fff", + INIT_11 = x"7db07dd57df97e1c7e3e7e5e7e7e7e9c7eb97ed47eef7f087f207f377f4c7f61", + INIT_12 = x"7ab57aee7b257b5c7b917bc47bf77c297c597c887cb67ce27d0e7d387d617d89", + INIT_13 = x"768d76d87722776b77b377f9783f788378c679087949798979c77a047a417a7c", + INIT_14 = x"7140719d71f9725472ae7306735e73b5740a745e74b17503755475a475f37640", + INIT_15 = x"6adb6b4a6bb76c236c8e6cf86d616dc96e306e956efa6f5e6fc07022708270e1", + INIT_16 = x"637063ee646b64e7656265dd665666ce674567bc683168a56919698b69fc6a6c", + INIT_17 = x"5b0f5b9c5c285cb35d3d5dc65e4f5ed65f5d5fe2606760eb616e61f0627162f1", + INIT_18 = x"51ce52685301539a543254c9555f55f45689571d57b0584258d3596359f35a81", + INIT_19 = x"47c34869490e49b34a574afa4b9d4c3f4ce04d804e204ebf4f5d4ffa50975133", + INIT_1a = x"3d073db73e673f163fc54073412041cd4279432543d0447a452345cc4674471c", + INIT_1b = x"31b4326d332633de3496354d360336b9376f382438d8398c3a3f3af23ba43c56", + INIT_1c = x"25e726a72767282628e529a32a612b1e2bdb2c982d542e102ecc2f86304130fb", + INIT_1d = x"19bd1a821b461c0b1ccf1d931e561f191fdc209f2161222322e423a624672527", + INIT_1e = x"0d530e1b0ee30fab10721139120012c7138e1455151b15e116a7176d183318f8", + INIT_1f = x"00c90192025b032403ed04b6057e0647071007d908a1096a0a320afb0bc30c8b", + INIT_20 = x"f43df505f5cef696f75ff827f8f0f9b9fa82fb4afc13fcdcfda5fe6eff370000", + INIT_21 = x"e7cde893e959ea1feae5ebabec72ed39ee00eec7ef8ef055f11df1e5f2adf375", + INIT_22 = x"db99dc5add1cddddde9fdf61e024e0e7e1aae26de331e3f5e4bae57ee643e708", + INIT_23 = x"cfbfd07ad134d1f0d2acd368d425d4e2d59fd65dd71bd7dad899d959da19dad9", + INIT_24 = x"c45cc50ec5c1c674c728c7dcc891c947c9fdcab3cb6acc22ccdacd93ce4ccf05", + INIT_25 = x"b98cba34baddbb86bc30bcdbbd87be33bee0bf8dc03bc0eac199c249c2f9c3aa", + INIT_26 = x"af69b006b0a3b141b1e0b280b320b3c1b463b506b5a9b64db6f2b797b83db8e4", + INIT_27 = x"a60da69da72da7bea850a8e3a977aa0caaa1ab37abceac66acffad98ae32aecd", + INIT_28 = x"9d8f9e109e929f159f99a01ea0a3a12aa1b1a23aa2c3a34da3d8a464a4f1a57f", + INIT_29 = x"9604967596e7975b97cf984498bb993299aa9a239a9e9b199b959c129c909d0f", + INIT_2a = x"8f7e8fde904090a29106916b91d09237929f9308937293dd944994b695259594", + INIT_2b = x"8a0d8a5c8aac8afd8b4f8ba28bf68c4b8ca28cfa8d528dac8e078e638ec08f1f", + INIT_2c = x"85bf85fc8639867786b786f8873a877d87c18807884d889588de8928897389c0", + INIT_2d = x"829f82c882f2831e834a837883a783d78409843c846f84a484db8512854b8584", + INIT_2e = x"80b480c980e080f88111812c81478164818281a281c281e48207822b82508277", + INIT_2f = x"800280048007800b80118018802080298033803f804c805a806a807a808c809f", + INIT_30 = x"808c807a806a805a804c803f80338029802080188011800b8007800480028001", + INIT_31 = x"8250822b820781e481c281a2818281648147812c811180f880e080c980b4809f", + INIT_32 = x"854b851284db84a4846f843c840983d783a78378834a831e82f282c8829f8277", + INIT_33 = x"8973892888de8895884d880787c1877d873a86f886b78677863985fc85bf8584", + INIT_34 = x"8ec08e638e078dac8d528cfa8ca28c4b8bf68ba28b4f8afd8aac8a5c8a0d89c0", + INIT_35 = x"952594b6944993dd93729308929f923791d0916b910690a290408fde8f7e8f1f", + INIT_36 = x"9c909c129b959b199a9e9a2399aa993298bb984497cf975b96e7967596049594", + INIT_37 = x"a4f1a464a3d8a34da2c3a23aa1b1a12aa0a3a01e9f999f159e929e109d8f9d0f", + INIT_38 = x"ae32ad98acffac66abceab37aaa1aa0ca977a8e3a850a7bea72da69da60da57f", + INIT_39 = x"b83db797b6f2b64db5a9b506b463b3c1b320b280b1e0b141b0a3b006af69aecd", + INIT_3a = x"c2f9c249c199c0eac03bbf8dbee0be33bd87bcdbbc30bb86baddba34b98cb8e4", + INIT_3b = x"ce4ccd93ccdacc22cb6acab3c9fdc947c891c7dcc728c674c5c1c50ec45cc3aa", + INIT_3c = x"da19d959d899d7dad71bd65dd59fd4e2d425d368d2acd1f0d134d07acfbfcf05", + INIT_3d = x"e643e57ee4bae3f5e331e26de1aae0e7e024df61de9fdddddd1cdc5adb99dad9", + INIT_3e = x"f2adf1e5f11df055ef8eeec7ee00ed39ec72ebabeae5ea1fe959e893e7cde708", + INIT_3f = x"ff37fe6efda5fcdcfc13fb4afa82f9b9f8f0f827f75ff696f5cef505f43df375" + ) + port map ( + WE => '0', + EN => en, + SSR => rst_i, + CLK => clk_i, + ADDR => adr_i, + DI => (others => '0'), + DIP => (others => '0'), + DOP => open, + DO => dat_o + ); + +end behavioral; diff --git a/projects/mp3tape/nexys2.vhd b/projects/mp3tape/nexys2.vhd new file mode 100644 index 0000000..09eb77f --- /dev/null +++ b/projects/mp3tape/nexys2.vhd @@ -0,0 +1,237 @@ +library ieee; +use ieee.std_logic_1164.all; + +library utility; +library dsp; +library nexys2_lib; + + +entity nexys2 is + port ( + clk_50: in std_logic; + + EppDB_DstmDB: inout std_logic_vector(7 downto 0); + EppWRITE: in std_logic; + EppASTB_DstmFLAGA: in std_logic; + EppDSTB_DstmFLAGB: in std_logic; + EppWAIT_DstmSLRD: out std_logic; + DstmIFCLK: in std_logic; + DstmSLCS: in std_logic; + DstmADR: out std_logic_vector(1 downto 0); + DstmSLWR: out std_logic; + DstmSLOE: out std_logic; + DstmPKTEND: out std_logic; + UsbMode: in std_logic; + UsbRdy: in std_logic; + + seg: out std_logic_vector(6 downto 0); + dp: out std_logic; + an: out std_logic_vector(3 downto 0); + Led: out std_logic_vector(7 downto 0); + sw: in std_logic_vector(7 downto 0); + + JA: inout std_logic_vector(7 downto 0) + ); +end nexys2; + + +architecture behavioral of nexys2 is + + signal rst_50: std_logic; -- Reset within clk_50 domain + + -- USB interfaces + signal wb_cyc: std_logic; + signal wb_stb: std_logic; + signal wb_we: std_logic; + signal wb_ack: std_logic; + signal wb_adr: std_logic_vector(7 downto 0); + signal wb_miso: std_logic_vector(7 downto 0); + signal wb_mosi: std_logic_vector(7 downto 0); + + signal dl_stb: std_logic; + signal dl_rdy: std_logic; + signal dl_dat: std_logic_vector(7 downto 0); + signal ul_stb: std_logic; + signal ul_rdy: std_logic; + signal ul_dat: std_logic_vector(7 downto 0); + + -- I2S interface + signal i2s_rx_l_dat: std_logic_vector(23 downto 0); + signal i2s_rx_r_dat: std_logic_vector(23 downto 0); + signal i2s_tx_l_dat: std_logic_vector(23 downto 0); + signal i2s_tx_r_dat: std_logic_vector(23 downto 0); + + -- Control signals + signal debug_to_host: std_logic_vector(63 downto 0); + signal debug_from_host: std_logic_vector(63 downto 0); + + -- Audio pipelines + signal audio_dl_stb: std_logic; + signal audio_dl_rdy: std_logic; + signal audio_dl_dat: std_logic_vector(31 downto 0); + + signal audio_ul_stb: std_logic; + signal audio_ul_rdy: std_logic; + signal audio_ul_dat: std_logic_vector(31 downto 0); + + signal audio_i_stb: std_logic; + signal audio_i_rdy: std_logic; + signal audio_i_dat: std_logic_vector(31 downto 0); + + signal audio_o_stb: std_logic; + signal audio_o_rdy: std_logic; + signal audio_o_dat: std_logic_vector(31 downto 0); + +begin + + seg <= (others => '1'); + dp <= '1'; + an <= (others => '1'); + Led <= (others => '0'); + + ---------------------------------------------------------------------------- + -- Processing pipeline + + e_effect: entity dsp.pcm16_2ch_tapeeffect + port map ( + rst_i => rst_50, + clk_i => clk_50, + + en_rolloff => sw(0), + en_flutter => sw(1), + en_wow => sw(2), + en_noise => sw(3), + + stb_i => audio_dl_stb, + rdy_o => audio_dl_rdy, + dat_i => audio_dl_dat, + + stb_o => audio_o_stb, + rdy_i => audio_o_rdy, + dat_o => audio_o_dat + ); + + audio_ul_stb <= audio_i_stb; + audio_ul_rdy <= audio_i_rdy; + audio_ul_dat <= audio_i_dat; + + + ---------------------------------------------------------------------------- + -- USB interfaces + + e_por_clk50: entity utility.power_on_reset_opt + port map ( + rst_i => '0', + clk_i => clk_50, + rst_o => rst_50 + ); + + e_usb: entity nexys2_lib.usb + port map ( + rst_i => rst_50, + + EppDB_DstmDB => EppDB_DstmDB, + EppWRITE => EppWRITE, + EppASTB_DstmFLAGA => EppASTB_DstmFLAGA, + EppDSTB_DstmFLAGB => EppDSTB_DstmFLAGB, + EppWAIT_DstmSLRD => EppWAIT_DstmSLRD, + DstmIFCLK => DstmIFCLK, + DstmSLCS => DstmSLCS, + DstmADR => DstmADR, + DstmSLWR => DstmSLWR, + DstmSLOE => DstmSLOE, + DstmPKTEND => DstmPKTEND, + UsbMode => UsbMode, + UsbRdy => UsbRdy, + + epp_clk_i => clk_50, + epp_cyc_o => wb_cyc, + epp_stb_o => wb_stb, + epp_we_o => wb_we, + epp_ack_i => wb_ack, + epp_adr_o => wb_adr, + epp_dat_i => wb_miso, + epp_dat_o => wb_mosi, + + stm_clk_i => clk_50, + stm_dl_stb_o => dl_stb, + stm_dl_ack_i => dl_rdy, + stm_dl_dat_o => dl_dat, + stm_ul_stb_i => ul_stb, + stm_ul_ack_o => ul_rdy, + stm_ul_dat_i => ul_dat + ); + + e_debug: entity utility.wb_debug + port map ( + rst_i => rst_50, + clk_i => clk_50, + + cyc_i => wb_cyc, + stb_i => wb_stb, + we_i => wb_we, + ack_o => wb_ack, + adr_i => wb_adr(2 downto 0), + dat_i => wb_mosi, + dat_o => wb_miso, + + debug_i => debug_to_host, + debug_o => debug_from_host + ); + + e_deser: entity dsp.deserialize + generic map (WIDTH => 8, N => 4) + port map ( + rst_i => rst_50, + clk_i => clk_50, + + stb_i => dl_stb, + rdy_o => dl_rdy, + dat_i => dl_dat, + + stb_o => audio_dl_stb, + rdy_i => audio_dl_rdy, + dat_o => audio_dl_dat + ); + + e_ser: entity dsp.serialize + generic map (WIDTH => 8, N => 4) + port map ( + rst_i => rst_50, + clk_i => clk_50, + + stb_i => audio_ul_stb, + rdy_o => audio_ul_rdy, + dat_i => audio_ul_dat, + + stb_o => ul_stb, + rdy_i => ul_rdy, + dat_o => ul_dat + ); + + + ---------------------------------------------------------------------------- + -- I2S interface + + e_i2s_pmod: entity dsp.i2s_pmod + port map ( + rst_50 => rst_50, + clk_50 => clk_50, + + stb_i => audio_o_stb, + rdy_o => audio_o_rdy, + l_dat_i => i2s_tx_l_dat, + r_dat_i => i2s_tx_r_dat, + + stb_o => audio_i_stb, + rdy_i => audio_i_rdy, + l_dat_o => i2s_rx_l_dat, + r_dat_o => i2s_rx_r_dat, + + pmod => JA + ); + i2s_tx_r_dat <= audio_o_dat(31 downto 16) & x"00"; + i2s_tx_l_dat <= audio_o_dat(15 downto 0) & x"00"; + audio_i_dat <= i2s_rx_r_dat(23 downto 8) & i2s_rx_l_dat(23 downto 8); + +end behavioral; diff --git a/projects/mp3tape/test_tapeeffect.vhd b/projects/mp3tape/test_tapeeffect.vhd new file mode 100644 index 0000000..072d662 --- /dev/null +++ b/projects/mp3tape/test_tapeeffect.vhd @@ -0,0 +1,72 @@ +library ieee; +use ieee.std_logic_1164.all; + +library dsp; + + +entity test_tapeeffect is +end test_tapeeffect; + + +architecture behavior of test_tapeeffect is + + constant CLK_I_PERIOD: time := 20 ns; + + signal rst_i: std_logic; + signal clk_i: std_logic; + + signal stb_i: std_logic; + signal rdy_o: std_logic; + signal dat_i: std_logic_vector(31 downto 0); + + signal stb_o: std_logic; + signal rdy_i: std_logic; + signal dat_o: std_logic_vector(31 downto 0); + +begin + + p_test: process + begin + -- Initial values + stb_i <= '0'; + rdy_i <= '1'; + + -- Reset + rst_i <= '1'; + wait for CLK_I_PERIOD*17; + rst_i <= '0'; + + -- Test + + -- Done + wait; + end process; + + e_uut: entity dsp.pcm16_2ch_tapeeffect + port map ( + rst_i => rst_i, + clk_i => clk_i, + + en_rolloff => '0', + en_flutter => '0', + en_wow => '0', + en_noise => '1', + + stb_i => stb_i, + rdy_o => rdy_o, + dat_i => dat_i, + + stb_o => stb_o, + rdy_i => rdy_i, + dat_o => dat_o + ); + + p_clk: process + begin + clk_i <= '0'; + wait for CLK_I_PERIOD/2; + clk_i <= '1'; + wait for CLK_I_PERIOD/2; + end process; + +end; diff --git a/projects/mp3tape/tests/test_nexys2.vhd b/projects/mp3tape/tests/test_nexys2.vhd new file mode 100644 index 0000000..6e28142 --- /dev/null +++ b/projects/mp3tape/tests/test_nexys2.vhd @@ -0,0 +1,104 @@ +library ieee; +use ieee.std_logic_1164.all; + +library simulated; +library work; + + +entity test_nexys2 is +end test_nexys2; + + +architecture behavior of test_nexys2 is + + constant CLK_50_PERIOD: time := 20 ns; + + signal clk_50: std_logic; + + signal ifclk: std_logic; + signal slcs: std_logic; + signal flaga: std_logic; + signal flagb: std_logic; + signal slrd: std_logic; + signal slwr: std_logic; + signal sloe: std_logic; + signal pktend: std_logic; + signal fifoadr: std_logic_vector(1 downto 0); + signal db: std_logic_vector(7 downto 0); + + signal host_dl_data: std_logic_vector(7 downto 0); + signal host_ul_count: integer; + + signal sw: std_logic_vector(7 downto 0); + signal JA: std_logic_vector(7 downto 0); + +begin + + p_test: process + begin + sw(7 downto 0) <= (others => '0'); + sw(3) <= '1'; -- Enable noise + wait; + end process; + + + e_uut: entity work.nexys2 + port map ( + clk_50 => clk_50, + EppDB_DstmDB => db, + EppWRITE => '1', + EppASTB_DstmFLAGA => flaga, + EppDSTB_DstmFLAGB => flagb, + EppWAIT_DstmSLRD => slrd, + DstmIFCLK => ifclk, + DstmSLCS => slcs, + DstmADR => fifoadr, + DstmSLWR => slwr, + DstmSLOE => sloe, + DstmPKTEND => pktend, + UsbMode => '0', + UsbRdy => '1', + seg => open, + dp => open, + an => open, + Led => open, + sw => sw, + JA => JA + ); + + + e_stm: entity simulated.stmhost + port map ( + ifclk => ifclk, + slcs => slcs, + flaga => flaga, + flagb => flagb, + slrd => slrd, + slwr => slwr, + sloe => sloe, + pktend => pktend, + fifoadr => fifoadr, + db => db, + + host_dl_data => host_dl_data, + host_ul_count => host_ul_count + ); + + + e_i2s: entity simulated.proto_i2s_tx + port map ( + sck => JA(6), + ws => JA(5), + sd => JA(7) + ); + + + p_clk_50: process + begin + clk_50 <= '0'; + wait for CLK_50_PERIOD/2; + clk_50 <= '1'; + wait for CLK_50_PERIOD/2; + end process; + +end; diff --git a/projects/mp3tape/tools/fft.py b/projects/mp3tape/tools/fft.py new file mode 100755 index 0000000..495d867 --- /dev/null +++ b/projects/mp3tape/tools/fft.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 + +from __future__ import annotations +from typing import * + +import math +from numpy.fft import fft, ifft + +# ------------------------------------------------------------------------------ + + +class C(object): + def __init__(self, r: float, i: float): + self.r, self.i = r, i + + def __add__(self, other: C): + if isinstance(other, int) or isinstance(other, float): + other = C(other, 0) + return C(self.r+other.r, self.i+other.i) + + def __neg__(self): + return C(-self.r, -self.i) + + def __sub__(self, other): + if isinstance(other, int) or isinstance(other, float): + other = C(other, 0) + return C(self.r-other.r, self.i-other.i) + + def __mul__(self, other): + if isinstance(other, int) or isinstance(other, float): + other = C(other, 0) + return C(self.r*other.r - self.i*other.i, self.r*other.i + self.i*other.r) + + def magnitude(self): + return math.sqrt(self.r*self.r + self.i*self.i) + + def __str__(self): + return f'{self.r:7.3f} + {self.i:7.3f}i' + + def __repr__(self): + return str(self) + + @staticmethod + def nth_root_of_unity(n: float): + # Will have one period within N samples + phi = 2*math.pi/n + return C(math.cos(phi), math.sin(phi)) + + +# ------------------------------------------------------------------------------ +# Examples from: +# https://www.youtube.com/watch?v=h7apO7q16V0 + + +def mul_poly(poly_coef_a: List[int], poly_coef_b: List[int]) -> List[int]: + """O(N^2) algorithm to multiply polynomials in coefficient form (functionally, a convolution)""" + poly_coef_c = [0] * (len(poly_coef_a)+len(poly_coef_b)-1) + for i, coef_a in enumerate(poly_coef_a): + for j, coef_b in enumerate(poly_coef_b): + poly_coef_c[i+j] += coef_a*coef_b + return poly_coef_c + + +def eval_poly(coef: List[C], x: C) -> C: + xn = C(1, 0) + p = C(0, 0) + for c in coef: + p += c * xn + xn *= x + return p + + +def dft_naive(coef: List[C]) -> List[C]: + out = list() + n = len(coef) + n2 = int(n/2) + for k in range(n): # Number of periods + e = C(0, 0) # Evaluated polynomial at x = nru + for i in range(n): # Sample index + phi = -2*math.pi*k*i/n + + # principal-nth-root-of-unity-with-k-periods-within-n-samples to the power of i + # k/n-th root of unity? + im = math.sin(phi) + re = math.cos(phi) + nru = C(re, im) # principal-nth-root-of-unity to the power of i + e += coef[i] * nru + out.append(e) + return out + + +def fft_recursive(coef: List[C]) -> List[C]: + n = len(coef) + if n <= 1: + return coef + n2 = int(n/2) + + coef_even = coef[0::2] + coef_odd = coef[1::2] + + even = fft_recursive(coef_even) + odd = fft_recursive(coef_odd) + + out = [C(0,0)]*n + for k in range(n2): + phi = -2*math.pi*k/n + re = math.cos(phi) + im = math.sin(phi) + w = C(re, im) + + out[k] = even[k] + w*odd[k] + out[k+n2] = even[k] - w*odd[k] + + return out + + +def ifft_recursive(coef: List[C]) -> List[C]: + n = len(coef) + if n <= 1: + return coef + n2 = int(n/2) + + coef_even = coef[0::2] + coef_odd = coef[1::2] + + even = ifft_recursive(coef_even) + odd = ifft_recursive(coef_odd) + + out = [C(0,0)]*n + for k in range(n2): + phi = 2*math.pi*k/n + re = math.cos(phi) + im = math.sin(phi) + w = C(re, im) + + out[k] = (even[k] + w*odd[k]) * 0.5 + out[k+n2] = (even[k] - w*odd[k]) * 0.5 + + return out + + +def reorder(c: List[Any]) -> List[Any]: + if len(c) <= 1: + return c + return reorder(c[0::2]) + reorder(c[1::2]) + + +def fft_iter(coef: List[C]) -> List[Any]: + coef = reorder(coef) # Pretend this isn't recursive, or that we did it ahead of time + # On an FPGA, the reordering can be done by reversing the bits of the index while storing into a buffer + n = len(coef) + + phis = [-2*math.pi*k/n for k in range(n)] + res = [math.cos(phi) for phi in phis] + ims = [math.sin(phi) for phi in phis] + ws = [C(re, im) for re, im in zip(res, ims)] + + size = 2 + while size <= n: # Each stage in the butterfly + s2 = int(size/2) + num_groups = n / size + for i in range(0, n, size): # Each group in the butterfly + for k in range(s2): # Samples within each group of the butterfly + a = coef[i+k] + b = coef[i+k+s2] * ws[int(k*num_groups)] + coef[i+k] = a + b + coef[i+k+s2] = a - b + size *= 2 + return coef + + +def ifft_iter(coef: List[C]) -> List[Any]: + coef = reorder(coef) # Pretend this isn't recursive, or that we did it ahead of time + # On an FPGA, the reordering can be done by reversing the bits of the index while storing into a buffer + n = len(coef) + + phis = [2*math.pi*k/n for k in range(n)] + res = [math.cos(phi) for phi in phis] + ims = [math.sin(phi) for phi in phis] + ws = [C(re, im) for re, im in zip(res, ims)] + + size = 2 + while size <= n: # Each stage in the butterfly + s2 = int(size/2) + num_groups = n / size + for i in range(0, n, size): # Each group in the butterfly + for k in range(s2): # Samples within each group of the butterfly + a = coef[i+k] + b = coef[i+k+s2] * ws[int(k*num_groups)] + coef[i+k] = (a + b) * 0.5 # Multiplying by 1/2 a total of log2(N) times turns into multiplying by 1/N when N = 2^x + coef[i+k+s2] = (a - b) * 0.5 + size *= 2 + return coef + + +# ------------------------------------------------------------------------------ + + +def main() -> int: + # A(x) = x^2 + 3x + 2, B(x) = 2x^2 + 1 + # print(mul_poly([2, 3, 1], [1, 0, 2])) + + n = 8 + + a = [math.cos(i*2*math.pi/n) for i in range(n)] + b = fft(a) + b = [C(i.real, i.imag) for i in b] + # # c = ifft(b) + # + #a2 = [C(a[i],0) for i in range(n)] + #a2 = [C(i,0) for i in range(n)] + a2 = [C(i,0) for i in [6, 2, 8, 4, 2, 8, 3, 6]] + b1 = dft_naive(a2) + b0 = fft_recursive(a2) + c0 = ifft_recursive(b0) + + fft_iter(a2) + + for i in a2: + print(i) + print() + print('My FFT:') + for i in b0: + print(i) + print() + print('My naive DFT:') + for i in b1: + print(i) + print() + print('"real" FFT:') + for i in b: + print(i) + print() + print('My iFFT:') + for i in c0: + print(i) + + print() + print('Iterative FFT:') + b2 = fft_iter(a2) + for i in b2: + print(i) + print() + print('Iterative iFFT:') + c2 = ifft_iter(b2) + for i in c2: + print(i) + + return 0 + + +if __name__ == '__main__': + exit(main()) diff --git a/projects/mp3tape/tools/jankman.py b/projects/mp3tape/tools/jankman.py new file mode 100755 index 0000000..c1129cc --- /dev/null +++ b/projects/mp3tape/tools/jankman.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 + +from __future__ import annotations +from typing import * + +import argparse +import struct + +import fft + +# ------------------------------------------------------------------------------ + + +def f_expect(f: BinaryIO, s: bytes): + d = f.read(len(s)) + assert d == s + + +def f_skipto(f: BinaryIO, s: bytes): + d = f.read(len(s)) + while d != s: + d = d[1:] + f.read(1) + + +def f_chomp_u32_le(f: BinaryIO): + return struct.unpack(' 32767: + v = 32767 + f.write(struct.pack(' int: + parser = argparse.ArgumentParser(description='Jankman pseudo-mp3 encoder/decoder') + subparsers = parser.add_subparsers(help='Operation', required=True, dest='cmd') + jankify = subparsers.add_parser('jankify', help='Convert WAV file to pseudo-mp3') + unjank = subparsers.add_parser('unjank', help='Convert pseudo-mp3 file to WAV') + jankify.add_argument('--input_file', '-i', required=True, help='Input filename') + jankify.add_argument('--output_file', '-o', required=True, help='Output filename') + unjank.add_argument('--input_file', '-i', required=True, help='Input filename') + unjank.add_argument('--output_file', '-o', required=True, help='Output filename') + + args = parser.parse_args() + if args.cmd == 'jankify': + with open(args.input_file, 'rb') as f: + with open(args.output_file, 'wb') as g: + do_jankify(f, g) + elif args.cmd == 'unjank': + with open(args.input_file, 'rb') as f: + with open(args.output_file, 'wb') as g: + do_unjank(f, g) + else: + parser.print_usage() + return 1 + + return 0 + + +if __name__ == '__main__': + exit(main()) -- 2.43.0