diff --git a/CompileSwitches.h b/CompileSwitches.h index 71949a6..52a9a05 100644 --- a/CompileSwitches.h +++ b/CompileSwitches.h @@ -3,4 +3,5 @@ //#define DEBUG_OUTPUT //#define STANDALONE_AUDIO #define PERF_CHECK +#define TARGET_TEENSY //#define SET_TEMPO diff --git a/GlitchDelayEffect.h b/GlitchDelayEffect.h index e3023a7..bb2d3ae 100644 --- a/GlitchDelayEffect.h +++ b/GlitchDelayEffect.h @@ -1,6 +1,7 @@ #pragma once -#include +#include "TeensyJuce.h" +#include "Util.h" #define DELAY_BUFFER_SIZE_IN_BYTES 1024*240 // 240k @@ -74,7 +75,7 @@ class DELAY_BUFFER { friend PLAY_HEAD; - byte m_buffer[DELAY_BUFFER_SIZE_IN_BYTES]; + uint8_t m_buffer[DELAY_BUFFER_SIZE_IN_BYTES]; int m_buffer_size_in_samples; int m_sample_size_in_bits; @@ -115,11 +116,10 @@ class DELAY_BUFFER //////////////////////////////////// -class GLITCH_DELAY_EFFECT : public AudioStream -{ +class GLITCH_DELAY_EFFECT : public TEENSY_AUDIO_STREAM_WRAPPER +{ static const int NUM_PLAY_HEADS = 3; - audio_block_t* m_input_queue_array[1]; DELAY_BUFFER m_delay_buffer; PLAY_HEAD m_play_heads[NUM_PLAY_HEADS]; @@ -136,12 +136,20 @@ class GLITCH_DELAY_EFFECT : public AudioStream int m_next_sample_size_in_bits; bool m_next_loop_moving; bool m_next_beat; + +protected: + + void process_audio_in_impl( int channel, const int16_t* sample_data, int num_samples ) override; + void process_audio_out_impl( int channel, int16_t* sample_data, int num_samples ) override; public: GLITCH_DELAY_EFFECT(); + + int num_input_channels() const override; + int num_output_channels() const override; - virtual void update(); + void update() override; void set_bit_depth( int sample_size_in_bits ); void set_speed( float speed ); diff --git a/GlitchDelayEffect.ino b/GlitchDelayEffect.ino index d1c5648..d4580f5 100644 --- a/GlitchDelayEffect.ino +++ b/GlitchDelayEffect.ino @@ -1,9 +1,12 @@ +#ifdef TARGET_TEENSY #include #include #include #include #include +#endif // TARGET_TEENSY +#include #include #include "GlitchDelayEffect.h" #include "CompileSwitches.h" @@ -12,6 +15,11 @@ const float MIN_SPEED( 0.25f ); const float MAX_SPEED( 4.0f ); +#ifdef TARGET_JUCE +const int AUDIO_BLOCK_SAMPLES( 512 ); // TODO need a value in JUCE, is it even constant? +const int AUDIO_SAMPLE_RATE( 44100 ); // TODO need a value in JUCE +#endif + const int FIXED_FADE_TIME_SAMPLES( (AUDIO_SAMPLE_RATE / 1000.0f ) * 4 ); // 4ms cross fade const int MIN_LOOP_SIZE_IN_SAMPLES( (FIXED_FADE_TIME_SAMPLES * 2) + AUDIO_BLOCK_SAMPLES ); const int MAX_LOOP_SIZE_IN_SAMPLES( AUDIO_SAMPLE_RATE * 0.5f ); @@ -76,6 +84,11 @@ PLAY_HEAD::PLAY_HEAD( const DELAY_BUFFER& delay_buffer, float play_speed ) : m_initial_loop_crossfade_complete(false) { set_loop_behind_write_head(); + + // set head immediately (don't want to crossfade initially) + m_current_play_head = m_destination_play_head; + m_initial_loop_crossfade_complete = true; + m_fade_samples_remaining = 0; } int PLAY_HEAD::current_position() const @@ -389,21 +402,21 @@ void PLAY_HEAD::disable_loop() #ifdef DEBUG_OUTPUT void PLAY_HEAD::debug_output() { - Serial.print("PLAY_HEAD current:"); - Serial.print(m_current_play_head); - Serial.print(" destination:"); - Serial.print(m_destination_play_head); - Serial.print(" loop start:"); - Serial.print(m_loop_start); - Serial.print(" loop end:"); - Serial.print(m_loop_end); - Serial.print(" fade samples:"); - Serial.print(m_fade_samples_remaining); + DEBUG_TEXT("PLAY_HEAD current:"); + DEBUG_TEXT(m_current_play_head); + DEBUG_TEXT(" destination:"); + DEBUG_TEXT(m_destination_play_head); + DEBUG_TEXT(" loop start:"); + DEBUG_TEXT(m_loop_start); + DEBUG_TEXT(" loop end:"); + DEBUG_TEXT(m_loop_end); + DEBUG_TEXT(" fade samples:"); + DEBUG_TEXT(m_fade_samples_remaining); if( !m_initial_loop_crossfade_complete ) { - Serial.print(" INITIAL CF"); + DEBUG_TEXT(" INITIAL CF"); } - Serial.print("\n"); + DEBUG_TEXT("\n"); } #endif @@ -696,8 +709,6 @@ void DELAY_BUFFER::debug_output() ///////////////////////////////////////////////////////////////////// GLITCH_DELAY_EFFECT::GLITCH_DELAY_EFFECT() : - AudioStream( 1, m_input_queue_array ), - m_input_queue_array(), m_delay_buffer(), m_play_heads( { PLAY_HEAD( m_delay_buffer, 0.5f ), PLAY_HEAD( m_delay_buffer, 1.0f ), PLAY_HEAD( m_delay_buffer, 2.0f ) } ), m_speed_ratio(1.0f), @@ -712,61 +723,70 @@ GLITCH_DELAY_EFFECT::GLITCH_DELAY_EFFECT() : } -void GLITCH_DELAY_EFFECT::update() -{ - m_delay_buffer.set_bit_depth( m_next_sample_size_in_bits ); - m_loop_moving = m_next_loop_moving; +void GLITCH_DELAY_EFFECT::process_audio_in_impl( int channel, const int16_t* sample_data, int num_samples ) +{ + ASSERT_MSG( channel == 0, "Only mono input supported" ); + + m_delay_buffer.write_to_buffer( sample_data, num_samples ); +} - for( int pi = 0; pi < NUM_PLAY_HEADS; ++pi ) - { - if( m_loop_moving ) - { - m_play_heads[pi].set_shift_speed( m_speed_ratio ); - } - else - { - m_play_heads[pi].set_shift_speed( 0.0f ); - m_play_heads[pi].set_jitter( m_speed_ratio ); - } - - m_play_heads[pi].set_loop_size( m_loop_size_ratio ); - - // check whether the write head is about to run over the read head, in which case cross fade read head to new position - if( m_play_heads[pi].position_inside_section( m_delay_buffer.write_head(), m_play_heads[pi].buffered_loop_start(), m_play_heads[pi].loop_end() ) ) - { - m_play_heads[pi].set_loop_behind_write_head(); - } - else if( m_next_beat && !m_play_heads[pi].crossfade_active() ) - { - m_play_heads[pi].set_next_loop(); - m_play_heads[pi].set_loop_behind_write_head(); - } - } - m_next_beat = false; - +void GLITCH_DELAY_EFFECT::process_audio_out_impl( int channel, int16_t* sample_data, int num_samples ) +{ - audio_block_t* read_block = receiveReadOnly(); + ASSERT_MSG( !m_play_heads[channel].position_inside_next_read( m_delay_buffer.write_head(), num_samples ), "Non - reading over write buffer\n" ); // position after write head is OLD DATA + m_play_heads[channel].read_from_play_head( sample_data, num_samples ); +} - if( read_block != nullptr ) - { - m_delay_buffer.write_to_buffer( read_block->data, AUDIO_BLOCK_SAMPLES ); - release( read_block ); +int GLITCH_DELAY_EFFECT::num_input_channels() const +{ + return 1; +} +int GLITCH_DELAY_EFFECT::num_output_channels() const +{ + return 3; +} + +void GLITCH_DELAY_EFFECT::update() +{ + m_delay_buffer.set_bit_depth( m_next_sample_size_in_bits ); + m_loop_moving = m_next_loop_moving; + for( int pi = 0; pi < NUM_PLAY_HEADS; ++pi ) { - audio_block_t* write_block = allocate(); - - if( write_block != nullptr ) - { - ASSERT_MSG( !m_play_heads[pi].position_inside_next_read( m_delay_buffer.write_head(), AUDIO_BLOCK_SAMPLES ), "Non - reading over write buffer\n" ); // position after write head is OLD DATA - m_play_heads[pi].read_from_play_head( write_block->data, AUDIO_BLOCK_SAMPLES ); + if( m_loop_moving ) + { + m_play_heads[pi].set_shift_speed( m_speed_ratio ); + } + else + { + m_play_heads[pi].set_shift_speed( 0.0f ); + m_play_heads[pi].set_jitter( m_speed_ratio ); + } + + m_play_heads[pi].set_loop_size( m_loop_size_ratio ); + + // check whether the write head is about to run over the read head, in which case cross fade read head to new position + if( m_play_heads[pi].position_inside_section( m_delay_buffer.write_head(), m_play_heads[pi].buffered_loop_start(), m_play_heads[pi].loop_end() ) ) + { + m_play_heads[pi].set_loop_behind_write_head(); + } + else if( m_next_beat && !m_play_heads[pi].crossfade_active() ) + { + m_play_heads[pi].set_next_loop(); + m_play_heads[pi].set_loop_behind_write_head(); + } + } + m_next_beat = false; - transmit( write_block, pi ); + // read in on channel 0 + process_audio_in( 0 ); - release( write_block ); // note is this legal? may want to allocate a new block each time - } + // write out all the playheads TODO this uses more output than there are channels!! + for( int pi = 0; pi < NUM_PLAY_HEADS; ++pi ) + { + process_audio_out( pi ); } - } } void GLITCH_DELAY_EFFECT::set_bit_depth( int sample_size_in_bits ) diff --git a/TeensyJuce.h b/TeensyJuce.h new file mode 100644 index 0000000..d0dec5a --- /dev/null +++ b/TeensyJuce.h @@ -0,0 +1,121 @@ +// +// TeensyJuce.h +// TeensyJuce +// +// Created by Scott Pitkethly on 23/09/2017. +// +// + +#ifndef TeensyJuce_h +#define TeensyJuce_h + +#include +#include "CompileSwitches.h" + +#ifdef TARGET_TEENSY + +#include + +class TEENSY_AUDIO_STREAM_WRAPPER : public AudioStream +{ + audio_block_t* m_input_queue_array[1]; + +protected: + + // these are the only functions that require bespoke Teensy code + bool process_audio_in( int channel ) + { + audio_block_t* read_block = receiveReadOnly(); + + if( read_block != nullptr ) + { + process_audio_in_impl( channel, read_block->data, AUDIO_BLOCK_SAMPLES ); + release( read_block ); + + return true; + } + + return false; + } + + bool process_audio_out( int channel ) + { + audio_block_t* write_block = allocate(); + + if( write_block != nullptr ) + { + process_audio_out_impl( channel, write_block->data, AUDIO_BLOCK_SAMPLES ); + + transmit( write_block, channel ); + + release( write_block ); + + return true; + } + + return false; + } + + // add audio processing code in these 2 functions + virtual void process_audio_in_impl( int channel, const int16_t* sample_data, int num_samples ) = 0; + virtual void process_audio_out_impl( int channel, int16_t* sample_data, int num_samples ) = 0; + +public: + + TEENSY_AUDIO_STREAM_WRAPPER() : + AudioStream( 1, m_input_queue_array ), + m_input_queue_array() + { + + } + + virtual ~TEENSY_AUDIO_STREAM_WRAPPER() {;} + + virtual int num_input_channels() const = 0; + virtual int num_output_channels() const = 0; +}; + +#endif // TARGET_TEENSY + +#ifdef TARGET_JUCE + +#include +#include "../JuceLibraryCode/JuceHeader.h" + +class TEENSY_AUDIO_STREAM_WRAPPER +{ + int m_num_input_channels; + int m_num_output_channels; + +protected: + + // store the 16-bit in/out buffers + typedef std::vector< int16_t > SAMPLE_BUFFER; + std::vector< SAMPLE_BUFFER > m_channel_buffers; + + // these are the only functions that require bespoke JUCE code + bool process_audio_in( int channel ); + bool process_audio_out( int channel ); + + // add audio processing code in these 2 functions + virtual void process_audio_in_impl( int channel, const int16_t* sample_data, int num_samples ) = 0; + virtual void process_audio_out_impl( int channel, int16_t* sample_data, int num_samples ) = 0; + +public: + + TEENSY_AUDIO_STREAM_WRAPPER(); + virtual ~TEENSY_AUDIO_STREAM_WRAPPER(); + + + void pre_process_audio( const AudioSampleBuffer& audio_in, int num_input_channels, int num_output_channels ); + void post_process_audio( AudioSampleBuffer& audio_out ); + + virtual int num_input_channels() const = 0; + virtual int num_output_channels() const = 0; + + virtual void update() = 0; +}; + +#endif // TARGET_JUCE + +#endif /* TeensyJuce_h */