├── Invention_no1.mp3 ├── Invention_no1_reverb.mp3 ├── .gitignore ├── AllpassFilter.h ├── FIRFilter.h ├── Makefile ├── FIRFilter.cpp ├── AllpassFilter.cpp ├── .vscode ├── launch.json └── settings.json ├── Template.h ├── FilterProject.h ├── LICENSE ├── Template.cpp ├── ffmpeg_play.cpp ├── FilterProject.cpp ├── ffmpeg_decode.cpp └── README.md /Invention_no1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sellicott/DSP-FFMpeg-Reverb/HEAD/Invention_no1.mp3 -------------------------------------------------------------------------------- /Invention_no1_reverb.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sellicott/DSP-FFMpeg-Reverb/HEAD/Invention_no1_reverb.mp3 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /AllpassFilter.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _ALLPASS_FILTER_H_ 3 | #define _ALLPASS_FILTER_H_ 4 | 5 | #include 6 | #include 7 | 8 | using std::size_t; 9 | using std::int32_t; 10 | 11 | using std::array; 12 | 13 | 14 | class AllpassFilter { 15 | using outType = int32_t; 16 | using deque = std::deque; 17 | using buffPtr = std::unique_ptr; 18 | 19 | public: 20 | AllpassFilter(size_t delay, float gain); 21 | ~AllpassFilter() = default; 22 | 23 | outType do_filtering(outType new_x); 24 | 25 | private: 26 | size_t delay; 27 | float gain; 28 | buffPtr delay_buff; 29 | }; 30 | 31 | #endif // _REVERB_UNIT_H_ -------------------------------------------------------------------------------- /FIRFilter.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _FIR_FILTER_H_ 3 | #define _FIR_FILTER_H_ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using std::size_t; 10 | using std::int32_t; 11 | 12 | using std::array; 13 | 14 | 15 | class FIRFilter { 16 | using outType = int32_t; 17 | using deque = std::deque; 18 | using buffPtr = std::unique_ptr; 19 | 20 | public: 21 | FIRFilter(std::vector taps_); 22 | ~FIRFilter() = default; 23 | 24 | outType do_filtering(outType new_x); 25 | 26 | private: 27 | 28 | size_t delay; 29 | std::vector taps; 30 | buffPtr input_samples; 31 | }; 32 | 33 | #endif // _REVERB_UNIT_H_ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC := FilterProject.cpp AllpassFilter.cpp FIRFilter.cpp 2 | 3 | CFLAGS = -O2 -g 4 | 5 | .PHONY: clean 6 | 7 | SRC_OBJ := $(SRC:.cpp=.o) 8 | 9 | .c.o: 10 | g++ -c $(CFLAGS) $< -o $@ 11 | 12 | .cpp.o: 13 | g++ -c $(CFLAGS) $< -o $@ 14 | 15 | all: $(SRC_OBJ) ffmpeg_decode ffmpeg_play ffmpeg_filter 16 | 17 | ffmpeg_decode: ffmpeg_decode.cpp ffmpeg_decode.o Makefile 18 | g++ -o $@ $@.o -lavformat -lavcodec -lavutil -lswresample 19 | 20 | ffmpeg_play: ffmpeg_play.cpp ffmpeg_play.o Makefile 21 | g++ -o $@ $@.o -lavformat -lavcodec -lavdevice -lavutil 22 | 23 | ffmpeg_filter: $(SRC_OBJ) Makefile 24 | g++ $(SRC_OBJ) -o ffmpeg_filter 25 | 26 | clean: 27 | rm -f $(SRC_OBJ) ./ffmpeg_decode ./ffmpeg_filter ./ffmpeg_play *.o -------------------------------------------------------------------------------- /FIRFilter.cpp: -------------------------------------------------------------------------------- 1 | #include "FIRFilter.h" 2 | 3 | FIRFilter::FIRFilter(std::vector taps_) : 4 | taps(taps_) 5 | { 6 | // multiply by 2 to make room for the L/R audio channels 7 | delay = taps_.size()*2; 8 | 9 | // initilize buffer to zeros 10 | input_samples = std::make_unique(delay, 0.0); 11 | } 12 | 13 | FIRFilter::outType FIRFilter::do_filtering(outType new_x) { 14 | auto &x = *input_samples.get(); 15 | 16 | // replace the oldest value for x 17 | x.pop_back(); 18 | x.push_front(new_x); 19 | 20 | double new_val = 0; 21 | // implement the filter. 22 | for (auto i = 0; i < delay/2; ++i) { 23 | //multiply by 2 to account for LR channels 24 | new_val += taps[i]*x[2*i]; 25 | } 26 | 27 | //return the newest value for y 28 | return new_val; 29 | } -------------------------------------------------------------------------------- /AllpassFilter.cpp: -------------------------------------------------------------------------------- 1 | #include "AllpassFilter.h" 2 | 3 | AllpassFilter::AllpassFilter(size_t delay_, float gain_) { 4 | 5 | // multiply by 2 to make room for the L/R audio channels 6 | delay = delay_ * 2; 7 | gain = gain_; 8 | // initilize the buffer to zeros 9 | delay_buff = std::make_unique(delay, 0.0); 10 | } 11 | 12 | AllpassFilter::outType AllpassFilter::do_filtering(outType new_x) { 13 | // grab the delay line 14 | auto &g = *delay_buff.get(); 15 | 16 | //get the latest value to come through the delay line 17 | auto g_out = g.back(); 18 | g.pop_back(); 19 | 20 | // do the thing! follows the standard allpass filter structure 21 | auto g_in = new_x + gain*g_out; 22 | g.push_front(g_in); 23 | auto y = -gain*g_in + g_out; 24 | 25 | //return the newest value for y 26 | return y; 27 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(gdb) Launch", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/ffmpeg_reverb", 12 | "args": ["<", "bach"], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [], 16 | "externalConsole": false, 17 | "MIMode": "gdb", 18 | "setupCommands": [ 19 | { 20 | "description": "Enable pretty-printing for gdb", 21 | "text": "-enable-pretty-printing", 22 | "ignoreFailures": true 23 | } 24 | ] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /Template.h: -------------------------------------------------------------------------------- 1 | #ifndef _FILTER_PROJECT_H 2 | #define _FILTER_PROJECT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "AllpassFilter.h" 8 | #include "FIRFilter.h" 9 | 10 | using std::size_t; 11 | using std::uint8_t; 12 | using std::int16_t; 13 | using std::array; 14 | 15 | 16 | class FilterProject{ 17 | private: 18 | using outType = int16_t; 19 | using deque = std::deque; 20 | using buffPtr = std::unique_ptr; 21 | 22 | public: 23 | //constructor 24 | FilterProject(); 25 | 26 | // the decoder and playback programs need the take the samples in the form of 8-bit integers 27 | uint8_t* get_samples(uint8_t* samples, size_t num_samples); 28 | 29 | private: 30 | //define the names of your filters here 31 | AllpassFilter allpassFilter; 32 | FIRFilter firFilter; 33 | 34 | outType do_filtering(outType new_x); 35 | 36 | const size_t sample_rate = 44100; 37 | }; 38 | 39 | #endif //_FILTER_PROJECT_H -------------------------------------------------------------------------------- /FilterProject.h: -------------------------------------------------------------------------------- 1 | #ifndef _FILTER_PROJECT_H 2 | #define _FILTER_PROJECT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "AllpassFilter.h" 8 | #include "FIRFilter.h" 9 | 10 | using std::size_t; 11 | using std::uint8_t; 12 | using std::int16_t; 13 | using std::array; 14 | 15 | 16 | class FilterProject{ 17 | private: 18 | using outType = int16_t; 19 | using deque = std::deque; 20 | using buffPtr = std::unique_ptr; 21 | 22 | public: 23 | //constructor 24 | FilterProject(float feedbackGain_ = 0.25); 25 | 26 | // the decoder and playback programs need the take the samples in the form of 8-bit integers 27 | uint8_t* get_samples(uint8_t* samples, size_t num_samples); 28 | 29 | private: 30 | //define the names of the filters I will be using 31 | AllpassFilter allpass1; 32 | AllpassFilter allpass2; 33 | AllpassFilter allpass3; 34 | AllpassFilter allpass4; 35 | FIRFilter firFilter; 36 | buffPtr delay; 37 | outType do_filtering(outType new_x); 38 | 39 | float feedbackGain; 40 | const size_t sample_rate = 44100; 41 | }; 42 | 43 | #endif //_FILTER_PROJECT_H -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sam Ellicott 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "cctype": "cpp", 4 | "clocale": "cpp", 5 | "cmath": "cpp", 6 | "cstdarg": "cpp", 7 | "cstddef": "cpp", 8 | "cstdio": "cpp", 9 | "cstdlib": "cpp", 10 | "ctime": "cpp", 11 | "cwchar": "cpp", 12 | "cwctype": "cpp", 13 | "array": "cpp", 14 | "atomic": "cpp", 15 | "*.tcc": "cpp", 16 | "chrono": "cpp", 17 | "condition_variable": "cpp", 18 | "cstdint": "cpp", 19 | "deque": "cpp", 20 | "list": "cpp", 21 | "unordered_map": "cpp", 22 | "vector": "cpp", 23 | "exception": "cpp", 24 | "fstream": "cpp", 25 | "functional": "cpp", 26 | "initializer_list": "cpp", 27 | "iosfwd": "cpp", 28 | "iostream": "cpp", 29 | "istream": "cpp", 30 | "limits": "cpp", 31 | "memory": "cpp", 32 | "mutex": "cpp", 33 | "new": "cpp", 34 | "numeric": "cpp", 35 | "optional": "cpp", 36 | "ostream": "cpp", 37 | "ratio": "cpp", 38 | "sstream": "cpp", 39 | "stdexcept": "cpp", 40 | "streambuf": "cpp", 41 | "string_view": "cpp", 42 | "system_error": "cpp", 43 | "thread": "cpp", 44 | "type_traits": "cpp", 45 | "tuple": "cpp", 46 | "typeinfo": "cpp", 47 | "utility": "cpp", 48 | "valarray": "cpp", 49 | "algorithm": "cpp" 50 | } 51 | } -------------------------------------------------------------------------------- /Template.cpp: -------------------------------------------------------------------------------- 1 | /* Perform filtering on audio samples 2 | * - reads input from stdin and writes filtered samples to stdout 3 | * - two channels (front left, front right) 4 | * - samples in interleaved format (L R L R ...) 5 | * - samples are 16-bit signed integers (what the Rpi needs) 6 | * 7 | * Usage: 8 | * ./ffmpeg_decode cool_song.mp3 | ./filter | ./ffmpeg_play 9 | */ 10 | 11 | #include "Template.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using std::string; 22 | using std::array; 23 | using std::uint8_t; 24 | using std::int16_t; 25 | 26 | // Constructor implementation 27 | FilterProject::FilterProject() : 28 | //initilize filters 29 | allpassFilter(4410, 0.6), 30 | 31 | //add your comma separated filters to initilize here 32 | 33 | // pass in FIR coefficients to the FIR filter class 34 | firFilter({ 0.5, 0.7, 0.5 /* Put your comma separated filter coefficients here */}) 35 | 36 | // note the last filter does not have a comma after it 37 | { } 38 | 39 | FilterProject::outType FilterProject::do_filtering(outType new_x) { 40 | 41 | // run through the all pass filters 42 | auto allpass_out = allpassFilter.do_filtering(new_x); 43 | auto FIR_out = firFilter.do_filtering(new_x); 44 | 45 | // we want to use the allpass filter output 46 | auto y = allpass_out; 47 | return y; 48 | } 49 | 50 | // function to run on the samples from stdin 51 | uint8_t* FilterProject::get_samples(uint8_t* samples, size_t num_samples) { 52 | 53 | //convert the uint8_t samples to floating point 54 | auto samples_cast = reinterpret_cast(samples); 55 | auto num_samples_cast = num_samples/sizeof(int16_t); 56 | 57 | for (auto i = 0; i < num_samples_cast; i++) { 58 | //filters on! 59 | auto left_sample = samples_cast[i]; 60 | samples_cast[i] = do_filtering(left_sample); 61 | ++i; 62 | auto right_sample = samples_cast[i]; 63 | samples_cast[i] = do_filtering(right_sample); 64 | } 65 | 66 | return reinterpret_cast(samples); 67 | } 68 | 69 | 70 | 71 | // ---------------------------------------------main -------------------------------------------------- 72 | int main(int argc, char** argv) { 73 | if (argc != 1) { 74 | fprintf(stderr, "usage: ./ffmpeg_decode | %s | ./ffmpeg_play\n", argv[0]); 75 | exit(1); 76 | } 77 | 78 | //some constants 79 | const int BUFF_SIZE = 4096; 80 | array buffer; 81 | FilterProject filter; 82 | 83 | for (;;) { 84 | 85 | // read input buffer from stdin 86 | ssize_t ret = read(STDIN_FILENO, buffer.data(), buffer.size()); 87 | if (ret < 0) { 88 | fprintf(stderr, "read(stdin)\n"); 89 | exit(1); 90 | } 91 | 92 | //exit if out of data 93 | if (ret == 0) { 94 | break; 95 | } 96 | 97 | // do the filtering 98 | filter.get_samples(buffer.data(), buffer.size()); 99 | 100 | // write output buffer to stdout 101 | if (write(STDOUT_FILENO, buffer.data(), buffer.size()) != buffer.size()) { 102 | fprintf(stderr, "error: write(stdout)\n"); 103 | exit(1); 104 | } 105 | } 106 | 107 | return 0; 108 | } -------------------------------------------------------------------------------- /ffmpeg_play.cpp: -------------------------------------------------------------------------------- 1 | /* Read decoded audio samples from stdin and send them to ALSA using ffmpeg. 2 | * Input format: 3 | * - two channels (front left, front right) 4 | * - samples in interleaved format (L R L R ...) 5 | * - samples are little-endian 32-bit floats 6 | * - sample rate is 44100 7 | * 8 | * Usage: 9 | * ./ffmpeg_play < cool_song_samples 10 | */ 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | extern "C" { 17 | #include 18 | #include 19 | #include 20 | } 21 | 22 | int main(int argc, char** argv) { 23 | if (argc != 1) { 24 | fprintf(stderr, "usage: %s < input_file\n", argv[0]); 25 | exit(1); 26 | } 27 | 28 | const int in_channels = 2, in_samples = 512, sample_rate = 44100; 29 | const int bitrate = 64000; 30 | 31 | const int max_buffer_size = 32 | av_samples_get_buffer_size( 33 | NULL, in_channels, in_samples, AV_SAMPLE_FMT_S16, 1); 34 | 35 | // register supported formats and codecs 36 | av_register_all(); 37 | 38 | // register supported devices 39 | avdevice_register_all(); 40 | 41 | // find output format for ALSA device 42 | AVOutputFormat* fmt = av_guess_format("alsa", NULL, NULL); 43 | if (!fmt) { 44 | fprintf(stderr, "av_guess_format()\n"); 45 | exit(1); 46 | } 47 | 48 | // allocate empty format context 49 | // provides methods for writing output packets 50 | AVFormatContext* fmt_ctx = avformat_alloc_context(); 51 | if (!fmt_ctx) { 52 | fprintf(stderr, "avformat_alloc_context()\n"); 53 | exit(1); 54 | } 55 | 56 | // tell format context to use ALSA as ouput device 57 | fmt_ctx->oformat = fmt; 58 | 59 | // add stream to format context 60 | AVStream* stream = avformat_new_stream(fmt_ctx, NULL); 61 | if (!stream) { 62 | fprintf(stderr, "avformat_new_stream()\n"); 63 | exit(1); 64 | } 65 | 66 | // initialize stream codec context 67 | // format conetxt uses codec context when writing packets 68 | AVCodecContext* codec_ctx = stream->codec; 69 | assert(codec_ctx); 70 | codec_ctx->codec_id = AV_CODEC_ID_PCM_S16LE; 71 | codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO; 72 | codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16; 73 | codec_ctx->bit_rate = bitrate; 74 | codec_ctx->sample_rate = sample_rate; 75 | codec_ctx->channels = in_channels; 76 | codec_ctx->channel_layout = AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT; 77 | 78 | // allocate buffer for input samples 79 | uint8_t* buffer = (uint8_t*)av_malloc(max_buffer_size); 80 | assert(buffer); 81 | 82 | // initialze output device 83 | if (avformat_write_header(fmt_ctx, NULL) < 0) { 84 | fprintf(stderr, "avformat_write_header()\n"); 85 | exit(1); 86 | } 87 | 88 | for (;;) { 89 | memset(buffer, 0, max_buffer_size); 90 | 91 | // read input buffer from stdin 92 | ssize_t ret = read(STDIN_FILENO, buffer, max_buffer_size); 93 | if (ret < 0) { 94 | fprintf(stderr, "read(stdin)\n"); 95 | exit(1); 96 | } 97 | 98 | if (ret == 0) { 99 | break; 100 | } 101 | 102 | // create output packet 103 | AVPacket packet; 104 | av_init_packet(&packet); 105 | packet.data = buffer; 106 | packet.size = max_buffer_size; 107 | 108 | // write output packet to format context 109 | if (av_write_frame(fmt_ctx, &packet) < 0) { 110 | fprintf(stderr, "av_write_frame()\n"); 111 | exit(1); 112 | } 113 | } 114 | 115 | av_free(buffer); 116 | avformat_free_context(fmt_ctx); 117 | 118 | return 0; 119 | } 120 | -------------------------------------------------------------------------------- /FilterProject.cpp: -------------------------------------------------------------------------------- 1 | /* Perform filtering on audio samples 2 | * - reads input from stdin and writes filtered samples to stdout 3 | * - two channels (front left, front right) 4 | * - samples in interleaved format (L R L R ...) 5 | * - samples are 16-bit signed integers (what the Rpi needs) 6 | * 7 | * Usage: 8 | * ./ffmpeg_decode cool_song.mp3 | ./filter | ./ffmpeg_play 9 | */ 10 | 11 | #include "FilterProject.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using std::string; 22 | using std::array; 23 | using std::uint8_t; 24 | using std::int16_t; 25 | 26 | // Constructor implementation 27 | FilterProject::FilterProject(float feedbackGain_) : 28 | //initilize filters 29 | allpass1(4410, 0.6), 30 | allpass2(1470, -0.6), 31 | allpass3(490, 0.6), 32 | allpass4(163, -0.6), 33 | 34 | // pass in FIR coefficients to the FIR filter class 35 | firFilter({ 0.003369,0.002810,0.001758,0.000340,-0.001255,-0.002793,-0.004014, 36 | -0.004659,-0.004516,-0.003464,-0.001514,0.001148,0.004157,0.006986,0.009003, 37 | 0.009571,0.008173,0.004560,-0.001120,-0.008222,-0.015581,-0.021579,-0.024323, 38 | -0.021933,-0.012904,0.003500,0.026890,0.055537,0.086377,0.115331,0.137960, 39 | 0.150407,0.150407,0.137960,0.115331,0.086377,0.055537,0.026890,0.003500, 40 | -0.012904,-0.021933,-0.024323,-0.021579,-0.015581,-0.008222,-0.001120, 41 | 0.004560,0.008173,0.009571,0.009003,0.006986,0.004157,0.001148,-0.001514, 42 | -0.003464,-0.004516,-0.004659,-0.004014,-0.002793,-0.001255,0.000340, 43 | 0.001758,0.002810,0.003369 }) 44 | { 45 | delay = std::make_unique(3*2*2940, 0.0); 46 | 47 | feedbackGain = 0.25; 48 | if (feedbackGain_ > 0 && feedbackGain_ < 1) { 49 | feedbackGain = feedbackGain_; 50 | } 51 | } 52 | 53 | // function to run on the samples from stdin 54 | uint8_t* FilterProject::get_samples(uint8_t* samples, size_t num_samples) { 55 | 56 | //convert the uint8_t samples to floating point 57 | auto samples_cast = reinterpret_cast(samples); 58 | auto num_samples_cast = num_samples/sizeof(int16_t); 59 | 60 | for (auto i = 0; i < num_samples_cast; i++) { 61 | //filters on! 62 | auto left_sample = samples_cast[i]; 63 | samples_cast[i] = do_filtering(left_sample); 64 | ++i; 65 | auto right_sample = samples_cast[i]; 66 | samples_cast[i] = do_filtering(right_sample); 67 | } 68 | 69 | return reinterpret_cast(samples); 70 | } 71 | 72 | FilterProject::outType FilterProject::do_filtering(outType new_x) { 73 | auto &d = *delay.get(); 74 | 75 | // the coefficient on the d.back() sets how long the reverb 76 | // will sustain: larger = longer 77 | auto x = 0.7*new_x + feedbackGain*d.back(); 78 | 79 | // run through the all pass filters 80 | // chain the outputs end to end 81 | auto y1 = allpass1.do_filtering(x); 82 | auto y2 = allpass2.do_filtering(y1); 83 | auto y3 = allpass3.do_filtering(y2); 84 | auto y4 = allpass4.do_filtering(y3); 85 | auto y5 = firFilter.do_filtering(y4); 86 | 87 | d.pop_back(); 88 | d.push_front(y5); 89 | 90 | //add a bit of an FIR filter here, smooth the output 91 | auto reverb_signal = y5 + 0.5*d[2*2940] + 0.25*d[2*2*2940] + 0.125*d[3*2*2940]; 92 | auto y = 0.6*reverb_signal + new_x; 93 | return y; 94 | } 95 | 96 | 97 | // ---------------------------------------------main -------------------------------------------------- 98 | int main(int argc, char** argv) { 99 | if (argc != 1) { 100 | fprintf(stderr, "usage: ./ffmpeg_decode | %s | ./ffmpeg_play\n", argv[0]); 101 | exit(1); 102 | } 103 | 104 | //some constants 105 | const int BUFF_SIZE = 4096; 106 | array buffer; 107 | FilterProject reverb(0.25); 108 | 109 | for (;;) { 110 | 111 | // read input buffer from stdin 112 | ssize_t ret = read(STDIN_FILENO, buffer.data(), buffer.size()); 113 | if (ret < 0) { 114 | fprintf(stderr, "read(stdin)\n"); 115 | exit(1); 116 | } 117 | 118 | //exit if out of data 119 | if (ret == 0) { 120 | break; 121 | } 122 | 123 | // do the filtering 124 | reverb.get_samples(buffer.data(), buffer.size()); 125 | 126 | // write output buffer to stdout 127 | if (write(STDOUT_FILENO, buffer.data(), buffer.size()) != buffer.size()) { 128 | fprintf(stderr, "error: write(stdout)\n"); 129 | exit(1); 130 | } 131 | } 132 | 133 | return 0; 134 | } -------------------------------------------------------------------------------- /ffmpeg_decode.cpp: -------------------------------------------------------------------------------- 1 | /* Decode audio file using ffmpeg and write decoded samples to stdout. 2 | * Output format: 3 | * - two channels (front left, front right) 4 | * - samples in interleaved format (L R L R ...) 5 | * - samples are 32-bit floats 6 | * - sample rate is 44100 7 | * 8 | * Usage: 9 | * ./ffmpeg_decode cool_song.mp3 > cool_song_samples 10 | */ 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | extern "C" { 17 | #include 18 | #include 19 | #include 20 | } 21 | 22 | int main(int argc, char** argv) { 23 | if (argc != 2) { 24 | fprintf(stderr, "usage: %s input_file > output_file\n", argv[0]); 25 | exit(1); 26 | } 27 | 28 | const int out_channels = 2, out_samples = 512, sample_rate = 44100; 29 | 30 | const int max_buffer_size = 31 | av_samples_get_buffer_size( 32 | NULL, out_channels, out_samples, AV_SAMPLE_FMT_S16, 1); 33 | 34 | // register supported formats and codecs 35 | av_register_all(); 36 | 37 | // allocate empty format context 38 | // provides methods for reading input packets 39 | AVFormatContext* fmt_ctx = avformat_alloc_context(); 40 | assert(fmt_ctx); 41 | 42 | // determine input file type and initialize format context 43 | if (avformat_open_input(&fmt_ctx, argv[1], NULL, NULL) != 0) { 44 | fprintf(stderr, "error: avformat_open_input()\n"); 45 | exit(1); 46 | } 47 | 48 | // determine supported codecs for input file streams and add 49 | // them to format context 50 | if (avformat_find_stream_info(fmt_ctx, NULL) < 0) { 51 | fprintf(stderr, "error: avformat_find_stream_info()\n"); 52 | exit(1); 53 | } 54 | 55 | #if 0 56 | av_dump_format(fmt_ctx, 0, argv[1], false); 57 | #endif 58 | 59 | // find audio stream in format context 60 | size_t stream = 0; 61 | for (; stream < fmt_ctx->nb_streams; stream++) { 62 | if (fmt_ctx->streams[stream]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { 63 | break; 64 | } 65 | } 66 | if (stream == fmt_ctx->nb_streams) { 67 | fprintf(stderr, "error: no audio stream found\n"); 68 | exit(1); 69 | } 70 | 71 | // get codec context for audio stream 72 | // provides methods for decoding input packets received from format context 73 | AVCodecContext* codec_ctx = fmt_ctx->streams[stream]->codec; 74 | assert(codec_ctx); 75 | 76 | if (codec_ctx->channel_layout == 0) { 77 | codec_ctx->channel_layout = AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT; 78 | } 79 | 80 | // find decoder for audio stream 81 | AVCodec* codec = avcodec_find_decoder(codec_ctx->codec_id); 82 | if (!codec) { 83 | fprintf(stderr, "error: avcodec_find_decoder()\n"); 84 | exit(1); 85 | } 86 | 87 | // initialize codec context with decoder we've found 88 | if (avcodec_open2(codec_ctx, codec, NULL) < 0) { 89 | fprintf(stderr, "error: avcodec_open2()\n"); 90 | exit(1); 91 | } 92 | 93 | // initialize converter from input audio stream to output stream 94 | // provides methods for converting decoded packets to output stream 95 | SwrContext* swr_ctx = 96 | swr_alloc_set_opts(NULL, 97 | AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT, // output 98 | AV_SAMPLE_FMT_S16, // output 99 | sample_rate, // output 100 | codec_ctx->channel_layout, // input 101 | codec_ctx->sample_fmt, // input 102 | codec_ctx->sample_rate, // input 103 | 0, 104 | NULL); 105 | if (!swr_ctx) { 106 | fprintf(stderr, "error: swr_alloc_set_opts()\n"); 107 | exit(1); 108 | } 109 | swr_init(swr_ctx); 110 | 111 | // create empty packet for input stream 112 | AVPacket packet; 113 | av_init_packet(&packet); 114 | packet.data = NULL; 115 | packet.size = 0; 116 | 117 | // allocate empty frame for decoding 118 | AVFrame* frame = av_frame_alloc(); 119 | assert(frame); 120 | 121 | // allocate buffer for output stream 122 | uint8_t* buffer = (uint8_t*)av_malloc(max_buffer_size); 123 | assert(buffer); 124 | 125 | // read packet from input audio file 126 | while (av_read_frame(fmt_ctx, &packet) >= 0) { 127 | // skip non-audio packets 128 | if (packet.stream_index != stream) { 129 | continue; 130 | } 131 | 132 | // decode packet to frame 133 | int got_frame = 0; 134 | if (avcodec_decode_audio4(codec_ctx, frame, &got_frame, &packet) < 0) { 135 | fprintf(stderr, "error: avcodec_decode_audio4()\n"); 136 | exit(1); 137 | } 138 | 139 | if (!got_frame) { 140 | continue; 141 | } 142 | 143 | // convert input frame to output buffer 144 | int got_samples = swr_convert( 145 | swr_ctx, 146 | &buffer, out_samples, 147 | (const uint8_t **)frame->data, frame->nb_samples); 148 | 149 | if (got_samples < 0) { 150 | fprintf(stderr, "error: swr_convert()\n"); 151 | exit(1); 152 | } 153 | 154 | while (got_samples > 0) { 155 | int buffer_size = 156 | av_samples_get_buffer_size( 157 | NULL, out_channels, got_samples, AV_SAMPLE_FMT_S16, 1); 158 | 159 | assert(buffer_size <= max_buffer_size); 160 | 161 | // write output buffer to stdout 162 | if (write(STDOUT_FILENO, buffer, buffer_size) != buffer_size) { 163 | fprintf(stderr, "error: write(stdout)\n"); 164 | exit(1); 165 | } 166 | 167 | // process samples buffered inside swr context 168 | got_samples = swr_convert(swr_ctx, &buffer, out_samples, NULL, 0); 169 | if (got_samples < 0) { 170 | fprintf(stderr, "error: swr_convert()\n"); 171 | exit(1); 172 | } 173 | } 174 | 175 | // free packet created by decoder 176 | av_free_packet(&packet); 177 | } 178 | 179 | av_free(buffer); 180 | av_frame_free(&frame); 181 | 182 | swr_free(&swr_ctx); 183 | 184 | avcodec_close(codec_ctx); 185 | avformat_close_input(&fmt_ctx); 186 | 187 | return 0; 188 | } 189 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DSP Filters project repo 2 | This project is heavily based on the work of github user gavv. 3 | His github repo is [here](https://github.com/gavv/snippets/tree/master/decode_play) 4 | see details in his [blog post](https://gavv.github.io/blog/decode-play/). 5 | 6 | If you want more in depth coverage about how my reverb unit works, checkout the article on 7 | [my blag](https://nebkelectronics.wordpress.com/2019/05/07/c-reverb-dsp-final-project/) 8 | 9 | 10 | # Overview 11 | 12 | This project implements a digital filter in the time domain in c++. 13 | This is accomplished by implementing the difference equation directly. 14 | For examples of this look at the AllpassFilter.{cpp,h} and FIRFilter.{cpp,h} files. 15 | an output variable (y) is generated by using the input and delayed versions of an 16 | intermediate variable. (This is a direct form 2 implementation, direct form 1 can 17 | be used but it requires a second delay buffer.) 18 | 19 | The project is split into three parts an audio decoding program (in ffmpeg_decode.cpp), 20 | a playback program (ffmpeg_play.cpp), and the actual filter (FilterProject.{cpp,h}). 21 | 22 | The decoding program outputs the raw samples from the audio file over stdout the filter program 23 | takes the samples and filters them (well, duh) then again outputs them over stdout. Last, the 24 | playback program takes the samples and does magic on them so the linux audio system can play 25 | it back. 26 | 27 | One gotcha is that since the files are typically stereo left and right samples are interleaved. 28 | this means that each buffer should be twice as long as designed, then every other index is used for each channel. 29 | For example to implement a 2nd order filter a buffer with a length of 6 is needed then indexes 30 | 0, 2,and 4 will be used for the left channel, and 1, 3, and 5 will be used for the right channel. 31 | 32 | **The filter should be implemented in the do_filtering() function.** 33 | 34 | Decoded samples are always in the same format: 35 | * linear PCM; 36 | * two channels (front Left, front Right); 37 | * interleaved format (L R L R ...); 38 | * samples are 16-bits sighed integers in little endian (actually CPU should be little-endian too); 39 | * sample rate is 44100 Hz. 40 | 41 | Note that the raspberry pi works much better if the GUI has been turned off (just google it). 42 | # Compiling the code 43 | 44 | A note on format any font that looks 45 | ``` 46 | like this (aka has three tick marks before and after it) 47 | ``` 48 | is meant to be typed into a terminal (command line) on the pi. Remember that pressing tab a few times auto-completes. 49 | 50 | To get the project working start with a raspberry pi with a bootable sd card. 51 | The pi should be running, logged in, with a keyboard and screen plugged connected, **and connected to the internet**. 52 | You probably should also disable the pi's GUI, as it isn't necessary, and will just bog down the pi. 53 | 54 | Steps to filtering goodness 55 | 1. Change the keyboard layout (it defaults to a UK layout) 56 | Run the following command in the terminal to change the layout. 57 | ``` 58 | sudo dpkg-reconfigure keyboard-configuration 59 | ``` 60 | 61 | 2. Reboot the pi (this makes the changes take effect) 62 | ``` 63 | sudo reboot 64 | ``` 65 | 66 | 3. Get my code from git 67 | ``` 68 | git clone https://github.com/sellicott/DSP-FFMpeg-Reverb.git 69 | ``` 70 | 71 | 4. Install the required packages (this is the audio decoding library) 72 | ``` 73 | sudo apt install libavcodec-dev libavformat-dev libavdevice-dev 74 | ``` 75 | This installs the three listed packages from the raspbian software library. 76 | 77 | 5. Next move into the directory and compile the code 78 | ``` 79 | cd DSP-FFMpeg-Reverb 80 | make 81 | ``` 82 | This will (hopefully) build all of the code needed to run your project. 83 | 84 | 6. Now you need some great music: 85 | 86 | There are two methods for copying files to the pi: SSH or USB. 87 | 88 | USB: 89 | 90 | Put your files on a flash drive and connect the drive to the pi. Next, copy the files 91 | into the project directory. 92 | 93 | Your flash drive will be in the /media/pi/name_of_your_drive folder on the pi. 94 | Assuming that your file is on the root directory of your flash drive: 95 | ``` 96 | cp /media/pi/name_of_drive/your_snazzy_audio_file.mp3 ./ 97 | ``` 98 | 99 | If this doesn't work, you will need to mount your drive to the directory structure of the pi. 100 | 101 | First check what drive name your USB drive is. This command will list all of the drives (the different letters), 102 | and all the partitions on them (the numbers). You will want to remember the highest letter drive avalible 103 | ``` 104 | ls /dev/sd?? 105 | ``` 106 | 107 | Now run the following command, replacing the question mark with the drive letter you found 108 | (it will probably be 'a' or 'b'). 109 | ``` 110 | sudo mount /dev/sd?1 /media/ 111 | cp /media/name_of_drive/your_snazzy_audio_file.mp3 ./ 112 | ``` 113 | 114 | SSH: 115 | 116 | First, you will have to enable SSH on the pi. Do the following on the pi with a keybord and mouse connected. 117 | Enter ```sudo raspi-config``` in a terminal window 118 | Select ```Interfacing Options``` 119 | Navigate to and select ```SSH``` 120 | Choose ```Yes``` 121 | Select ```Ok``` 122 | Choose ```Finish``` 123 | 124 | Now note down the pi's IP address by entering ```ifconfig``` and copying the number in the "eth0" section next to "inet". It should look something like "163.11.23x.xxx". 125 | 126 | Open WinSCP and enter your IP address in the host name section, then "pi" as the user name, and "raspberry" as the password. 127 | 128 | Now you can drag and drop to the right side, which should be showing your home directory. 129 | 130 | 7. Now you can run your filter 131 | The decoder, filter, and playback programs must be connected via pipe. 132 | (A pipe takes the output from stdout from one program and "pipes" it into 133 | the input for another program.) To use your filter run the following command. 134 | ``` 135 | ./ffmpeg_decode your_snazzy_audio_file.mp3 | ./ffmpeg_filter | ./ffmpeg_play 136 | ``` 137 | Note that the input audio file can be in almost any format. 138 | 139 | If you want to save the output of your filter to an mp3 file, substitute the following for ```./ffmpeg_play``` 140 | 141 | ``` 142 | ffmpeg -f s16le -ar 44.1k -ac 2 -i pipe:0 .mp3 143 | ``` 144 | 145 | Explanation of the command: 146 | * -f s16le sets the input audio format to signed 16bit little endian 147 | * -ar 44.1k sets the input audio rate to 44.1k (might need changed based on the input file) 148 | * -ac 2 sets the number of channels to 2 (might need changed) 149 | * -i pipe:0 sets the input to be the standard input pipe (the previous command in the chain) 150 | 151 | I don't think any of those settings will actually need changed, but there might be some files for which the output file sounds 152 | strange. 153 | 154 | # The Code 155 | In the FilterProject.cpp file the function do_filtering implements the filters for the project. 156 | (The main file calls another function get_samples() which calls do_filtering() in order to apply the filter to the buffer 157 | provided by the audio decoder program.) 158 | 159 | **I have made simple template for you to edit in Template.cpp and Template.h, when you want to compile it you shoud rename Template.cpp to FilterProject.cpp.** 160 | This will allow the makefile to build your code instead of my example code (which implements reverb). All of the comments below apply to the template code as 161 | well. 162 | 163 | **If you add any more files to the project you will have to add them to the ```SRC :=``` line in the Makfile** 164 | 165 | In my project (the reverb example) I used the do_filtering function to chain togeter all of the other filters I used 166 | (four allpass filters and a few FIR filters). These filters are implemented as c++ objects, meaining they need to be instantiated 167 | (they are defined near the end of FilterProject.h). I do this in the *base initilization section* 168 | (right after the beginning of the constructor, you remember CS1220 don't you?) of the FilterProject class. 169 | 170 | Here is an example (from Template.cpp) 171 | ```c++ 172 | FilterProject::FilterProject() : 173 | //initilize filters 174 | allpassFilter(4410, 0.6), 175 | 176 | //add your comma separated filters to initilize here 177 | 178 | // pass in FIR coefficients to the FIR filter class 179 | firFilter({ 0.5, 0.7, 0.5 /* Put your comma separated filter coefficients here */}) 180 | 181 | // note the last filter does not have a comma after it 182 | {} 183 | ``` 184 | 185 | The actual filters for the project are implemented in the files 186 | AllpassFilter.cpp, FIRFilter.cpp and their corresponding .h files AllpassFilter.h and FIRFilter.h. 187 | You can probably copy these files when implementing your own filter classes (or just use my FIR filter code as-is). 188 | 189 | ## Implementing Filters 190 | This following code snippet initilizes my allpass filter (from AllpassFilter.cpp) 191 | ```c++ 192 | AllpassFilter::outType AllpassFilter::do_filtering(outType new_x) { 193 | // grab the delay line 194 | auto &g = *delay_buff.get(); 195 | 196 | //get the latest value to come through the delay line 197 | auto g_out = g.back(); 198 | g.pop_back(); 199 | 200 | // do the thing! follows the standard allpass filter structure 201 | auto g_in = new_x + gain*g_out; 202 | g.push_front(g_in); 203 | auto y = -gain*g_in + g_out; 204 | 205 | //return the newest value for y 206 | return y; 207 | } 208 | ``` 209 | 210 | In general an IIR implementation would look something like this 211 | (Just implementing equation 4.110 and 4.111 from p269 of the textbook) 212 | note that the delay_buff deque needs to be 2x the lenth of your filter coefficients to make room 213 | for both left and right audio samples 214 | ```c++ 215 | //find g[n] each delay is an index in the array 0 is the latest value n is the oldest value 216 | //note that each index is multiplied by 2 because left and right audio samples are interleaved 217 | //also note that g[1] is actually g[n-1], and g[3] is g[n-2], etc 218 | auto n = g.size()/2; 219 | auto g_n = new_x + a1*g[1] + a2 * g[3] + /*...*/ a_n * g[2*n-1]; 220 | 221 | // pretty much the same thing here 222 | auto y = b0 * g_n + b1 * g[1] + /*...*/ b_n * g[3]; 223 | 224 | // add the value to the delay line 225 | g.pop_back(); 226 | g.push_front(g_n); 227 | 228 | //return the newest value for y 229 | return y; 230 | ``` 231 | 232 | The delay line should be constructed in a way similar to how I did it in the FIRFilter code 233 | Note that the delay_buff from the previous code examples serves the same purpose as input_samples, 234 | and they are constructed the same way. 235 | ```c++ 236 | // the taps input are the coefficients for the FIR filter 237 | FIRFilter::FIRFilter(std::vector taps_) : 238 | // initilize the class taps variable. 239 | taps(taps_) 240 | { 241 | // multiply by 2 to make room for the L/R audio channels 242 | delay = taps_.size()*2; 243 | 244 | // initilize buffer to zeros 245 | input_samples = std::make_unique(delay, 0.0); 246 | } 247 | ``` 248 | --------------------------------------------------------------------------------