Forum Rule: Always post complete source code & details to reproduce any issue!
Page 1 of 2 1 2 LastLast
Results 1 to 25 of 32

Thread: recording a loop while granular freezing

  1. #1

    recording a loop while granular freezing

    I started a sampler/looper project and right now I can record an audio loop and I setted up two grain object to freeze parts of the loop. The thing is now, when I have the freezing going on and I want to record a new loop, the whole audio stops for the recording. When I stop the recording the freeze is there as before the recording. I tried my luck with adding AudioNoInterrupts() at different points in the void recording() and void continuerecording() part but this did not help and dissabled the recording option.

    Here is the Code:

    Code:
    #include <Bounce.h>
    #include <Bounce.h>
    #include <Audio.h>
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    #include <SerialFlash.h>
    
    // GUItool: begin automatically generated code
    AudioInputI2S              in;           //xy=105,63
    AudioEffectFade            fade1;
    AudioEffectFade            fade2;
    AudioEffectGranular        granular1;
    AudioEffectGranular        granular2;
    AudioEffectGranular        granular3;
    AudioEffectFreeverbStereo  freeverbs1;
    AudioEffectEnvelope        envelope1;
    AudioEffectMultiply        multiply1;
    
    
    AudioSynthWaveform         waveform1;
    AudioSynthWaveformDc       dc1;
    
    AudioMixer4                mixer1;
    AudioMixer4                mixerL;
    AudioMixer4                mixerR;
    AudioMixer4                revSend1;
    AudioMixer4                granularMixL;
    AudioMixer4                granularMixR;
    AudioMixer4                mixer2grain2;
    AudioMixer4                mixer2grain3;
    AudioMixer4                mixerDc;
    
    
    AudioAnalyzePeak           peak1;          //xy=278,108
    AudioRecordQueue           queue1;         //xy=281,63
    AudioPlaySdRaw             playRaw1;       //xy=302,157
    AudioOutputI2S             out;           //xy=470,120
    AudioConnection            patchCord1(in, 0, queue1, 0);
    AudioConnection            patchCord2(in, 0, peak1, 0);
    AudioConnection            patchCord3(playRaw1, 0, fade1, 0);
    AudioConnection            patchCord4(playRaw1, 0, fade2, 0);
    AudioConnection            patchCord5(fade1, 0, mixer1, 0);
    AudioConnection            patchCord6(fade2, 0, mixer1, 1);
    AudioConnection            patchCord7(mixer1, 0, granular1, 0);
    AudioConnection            patchCord8(granular1, 0, envelope1, 0);
    AudioConnection            patchCord9(envelope1, 0, revSend1, 0);
    AudioConnection            patchCord10(revSend1, 0, freeverbs1, 0);
    AudioConnection            patchCord11(granular1, 0, mixerL, 0);
    AudioConnection            patchCord12(granular1, 0, mixerR, 0);
    AudioConnection            patchCord13(freeverbs1, 0, mixerL, 1);
    AudioConnection            patchCord14(freeverbs1, 1, mixerR, 1);
    AudioConnection            patchCord15(mixerL, 0, out, 0);
    AudioConnection            patchCord16(mixerR, 0, out, 1);
    
    // Granular Mix
    
    AudioConnection            patchCord17(granular1, 0, mixer2grain2, 0);
    AudioConnection            patchCord18(granular1, 0, mixer2grain3, 0);
    AudioConnection            patchCord19(mixer2grain2, 0, granular2, 0);
    AudioConnection            patchCord20(mixer2grain3, 0, granular3, 0);
    AudioConnection            patchCord21(granular2, 0, granularMixL, 0);
    AudioConnection            patchCord22(granular2, 0, granularMixR, 0);
    AudioConnection            patchCord23(multiply1, 0, granularMixL, 1);
    AudioConnection            patchCord24(multiply1, 0, granularMixR, 1);
    AudioConnection            patchCord25(granularMixL, 0, mixerL, 2);
    AudioConnection            patchCord26(granularMixR, 0, mixerR, 2);
    
    // VCA
    
    AudioConnection            patchCord27(granular3, 0, multiply1, 0);
    AudioConnection            patchCord28(waveform1, 0, mixerDc, 0);
    AudioConnection            patchCord29(dc1, 0, mixerDc, 1);
    AudioConnection            patchCord30(mixerDc, 0, multiply1, 1);
    
    AudioControlSGTL5000       sgtl5000_1;     //xy=265,212
    // GUItool: end automatically generated code
    
    // For a stereo recording version, see this forum thread:
    // https://forum.pjrc.com/threads/46150?p=158388&viewfull=1#post158388
    
    // A much more advanced sound recording and data logging project:
    // https://github.com/WMXZ-EU/microSoundRecorder
    // https://github.com/WMXZ-EU/microSoundRecorder/wiki/Hardware-setup
    // https://forum.pjrc.com/threads/52175?p=185386&viewfull=1#post185386
    
    // Bounce objects to easily and reliably read the buttons
    Bounce buttonRecord = Bounce(0, 8);
    Bounce buttonGrain =  Bounce(1, 8);  // 8 = 8 ms debounce time
    Bounce buttonPlay =   Bounce(2, 8);
    Bounce buttonGrain2 =   Bounce(3, 8);
    Bounce buttonGrain3 =   Bounce(28, 8);
    Bounce changeWaveform = Bounce(29, 15);
    
    #define GRANULAR_MEMORY_SIZE 10000  // enough for 290 ms at 44.1 kHz
    int16_t granularMemory[GRANULAR_MEMORY_SIZE];
    
    #define GRANULAR_MEMORY_SIZE2 10000  // enough for 290 ms at 44.1 kHz
    int16_t granularMemory2[GRANULAR_MEMORY_SIZE2];
    
    #define GRANULAR_MEMORY_SIZE3 10000  // enough for 290 ms at 44.1 kHz
    int16_t granularMemory3[GRANULAR_MEMORY_SIZE3];
    
    
    // which input on the audio shield will be used?
    const int myInput = AUDIO_INPUT_LINEIN;
    //const int myInput = AUDIO_INPUT_MIC;
    
    
    // Use these with the Teensy Audio Shield
    #define SDCARD_CS_PIN    10
    #define SDCARD_MOSI_PIN  7
    #define SDCARD_SCK_PIN   14
    
    
    // Remember which mode we're doing
    int mode = 0;  // 0=stopped, 1=recording, 2=playing
    int mode1 = 0;
    int mode2 = 0;  // 0=stopped, 1=recording, 2=playing
    
    int mode12 = 0;
    // The file where data is recorded
    File frec;
    
    bool State = false;
    bool Stateenv = false;
    int playmode = 0;
    int playmode2 = 0;
    unsigned long previousMillis = 0;
    long interval = 500;
    long interval1 = interval - 50;
    long intervalenv = 500;
    
    int current_waveform = 0;
    
    void setup() {
    
      granular1.begin(granularMemory, GRANULAR_MEMORY_SIZE);
      granular2.begin(granularMemory2, GRANULAR_MEMORY_SIZE2);
      granular3.begin(granularMemory3, GRANULAR_MEMORY_SIZE3);
    
      mixer1.gain(0, 0.8);
      mixer1.gain(1, 0.8);
      mixer1.gain(2, 0.8);
      mixerL.gain(0, 0.6);
      mixerR.gain(0, 0.6);
      mixerL.gain(1, 0.6);
      mixerR.gain(1, 0.6);
      mixerL.gain(2, 0.6);
      mixerR.gain(2, 0.6);
      mixer2grain2.gain(0, 0);
      mixer2grain3.gain(0, 0);
      granularMixL.gain(0, 0.7);
      granularMixR.gain(0, 0.3);
      granularMixL.gain(1, 0.3);
      granularMixR.gain(1, 0.7);
      mixerDc.gain(0, 1);
      mixerDc.gain(1, 1);
    
    
      // Configure the pushbutton pins
      pinMode(0, INPUT_PULLUP);
      pinMode(1, INPUT_PULLUP);
      pinMode(2, INPUT_PULLUP);
      pinMode(3, INPUT_PULLUP);
      pinMode(28, INPUT_PULLUP);
      pinMode(29, INPUT_PULLUP);
    
      // Audio connections require memory, and the record queue
      // uses this memory to buffer incoming audio.
      AudioMemory(240);
    
      // Enable the audio shield, select input, and enable output
      sgtl5000_1.enable();
      sgtl5000_1.inputSelect(myInput);
      sgtl5000_1.volume(0.5);
    
      // Initialize the SD card
      SPI.setMOSI(SDCARD_MOSI_PIN);
      SPI.setSCK(SDCARD_SCK_PIN);
      if (!(SD.begin(SDCARD_CS_PIN))) {
        // stop here if no SD card, but print a message
        while (1) {
          Serial.println("Unable to access the SD card");
          delay(500);
        }
      }
    
      current_waveform = WAVEFORM_SINE;
      waveform1.begin(current_waveform);
    }
    
    
    
    void loop() {
    
      /// Looplength
    
      unsigned long currentMillis = millis();
      interval = map (analogRead(A13), 0, 1023, 50, playRaw1.lengthMillis());
      if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
    
        if (State == false) {
          State = true;
        } else {
          State = false;
        }
      }
    
      if (currentMillis - previousMillis >= interval1) {
    
            if (Stateenv == false) {
              Stateenv = true;
            } else {
              Stateenv = false;
            }
      }
    
      env1();
      env2();
      grain();
      grain2();
      grain3();
      reverb();
      envelope();
      vca1();
    
      // First, read the buttons
      buttonRecord.update();
      buttonPlay.update();
    
    
    ///////////////// 1st Sample
    
      // Respond to button presses
      if (buttonRecord.fallingEdge()) {
        
        stopPlaying1();
        stopPlaying2();
        startRecording();
        playmode = 0;
        
      }
    
      if (buttonRecord.risingEdge()) {
         
        stopRecording();
        playmode = 1;
      }
    
    
      if (playmode == 1) {
    
        //play1
    
        if (State == true) {
          
          if (mode == 0) {
            startPlaying1();
          }
        }
    
        if (State == false) {
          stopPlaying1();
        }
    
        //play2
        if (State == false) {
    
          if (mode1 == 0) {
            startPlaying2();
          }
        }
    
        if (State == true) {
          stopPlaying2();
        }
      }
    
    
    
      // If we're playing or recording, carry on...
      if (mode == 1) {
     
        continueRecording();
      }
      if (mode == 2) {
        continuePlaying1();
    
      }
      if (mode1 == 2) {
        continuePlaying2();
      }
    
    
    }
    
    ////////////////// 1st Sample
    
    void startRecording() {
      Serial.println("startRecording");
      
      if (SD.exists("RECORD.RAW")) {
        // The SD library writes new data to the end of the
        // file, so to start a new recording, the old file
        // must be deleted before new data is written.
        SD.remove("RECORD.RAW");
      }
      frec = SD.open("RECORD.RAW", FILE_WRITE);
      if (frec) {
       
        queue1.begin();
        mode = 1;
      }
    }
    
    void continueRecording() {
      
      if (queue1.available() >= 2) {
        
        byte buffer[512];
        
        // Fetch 2 blocks from the audio library and copy
        // into a 512 byte buffer.  The Arduino SD library
        // is most efficient when full 512 byte sector size
        // writes are used.
        memcpy(buffer, queue1.readBuffer(), 256);
        queue1.freeBuffer();
        memcpy(buffer + 256, queue1.readBuffer(), 256);
        queue1.freeBuffer();
        // write all 512 bytes to the SD card
        //elapsedMicros usec = 0;
        frec.write(buffer, 512);
        // Uncomment these lines to see how long SD writes
        // are taking.  A pair of audio blocks arrives every
        // 5802 microseconds, so hopefully most of the writes
        // take well under 5802 us.  Some will take more, as
        // the SD library also must write to the FAT tables
        // and the SD card controller manages media erase and
        // wear leveling.  The queue1 object can buffer
        // approximately 301700 us of audio, to allow time
        // for occasional high SD card latency, as long as
        // the average write time is under 5802 us.
        //Serial.print("SD write, us=");
        //Serial.println(usec);
    
    
      }
    }
    
    void stopRecording() {
      
      Serial.println("stopRecording");
      AudioInterrupts();
      queue1.end();
      if (mode == 1) {
        while (queue1.available() > 0) {
          frec.write((byte*)queue1.readBuffer(), 256);
          queue1.freeBuffer();
        }
        frec.close();
      }
      mode = 0;
    }
    
    //playing1
    void startPlaying1() {
    
      playRaw1.play("RECORD.RAW");
      mode = 2;
    }
    
    void continuePlaying1() {
      if (!playRaw1.isPlaying()) {
        playRaw1.stop();
        mode = 0;
      }
    }
    
    void stopPlaying1() {
     
    
      if (mode == 2) playRaw1.stop();
      mode = 0;
    }
    
    //playing2
    void startPlaying2() {
    
      playRaw1.play("RECORD.RAW");
      mode1 = 2;
    }
    
    void continuePlaying2() {
      if (!playRaw1.isPlaying()) {
        playRaw1.stop();
        mode1 = 0;
      }
    }
    
    void stopPlaying2() {
    
      if (mode1 == 2) playRaw1.stop();
      mode1 = 0;
    }
    
    
    ////////////// Fades
    
    void env1() {
      if (Stateenv == false) {
    
        fade1.fadeOut(50);
      } else {
    
        fade1.fadeIn(50);
      }
    
    }
    
    void env2() {
      if (Stateenv == true) {
    
        fade2.fadeOut(50);
      } else {
    
        fade2.fadeIn(50);
      }
    
    }
    
    /////////// grain for main loop
    void grain() {
      buttonGrain.update();
    
     // float knobA12 = (float)analogRead(A12) / 1023.0;
      float knobA11 = (float)analogRead(A11) / 1023.0;
    
      // Button 1 starts Pitch Shift effect
      if (buttonGrain.risingEdge()) {
       
       
      //  float msec = 10.0 + (knobA12 * 100.0);
        granular1.beginPitchShift(1000); 
      }
    
       // Continuously adjust the speed, based on the A3 pot
      float ratio;
      //ratio = powf(2.0, knobA11 * 2.0 - 1.0); // 0.5 to 2.0
       ratio = powf(2.0, knobA11 * 6.0 - 3.0); // 0.125 to 8.0 -- uncomment for far too much range!
      granular1.setSpeed(ratio);
    
    }
    
    /////grain freeze 1
    void grain2() { 
        buttonGrain2.update();
                
      float knobA14 = (float)analogRead(A14) / 1023.0;
      float knobA10 = (float)analogRead(A10) / 1023.0;
        
      // Button 0 starts Freeze effect
      if (buttonGrain2.fallingEdge()) {
        mixer2grain2.gain(0, 1);
        float msec2 = 50.0 + (knobA14 * 150.0);
        granular2.beginFreeze(msec2);
        Serial.print("Begin granular freeze using ");
        Serial.print(msec2);
        Serial.println(" grains");
      }
      if (buttonGrain2.risingEdge()) {
        mixer2grain2.gain(0, 0);
        granular2.stop();
      }
    
      float ratio2;
      //ratio2 = powf(2.0, knobA10 * 2.0 - 1.0); // 0.5 to 2.0
      ratio2 = powf(2.0, knobA10 * 6.0 - 3.0); // 0.125 to 8.0 -- uncomment for far too much range!
      granular2.setSpeed(ratio2);
    
    }
    
    /////grainfreeze2
    void grain3() { 
        buttonGrain3.update();
                
      float knobA14 = (float)analogRead(A14) / 1023.0;
      float knobA10 = (float)analogRead(A10) / 1023.0;
        
      // Button 0 starts Freeze effect
      if (buttonGrain3.fallingEdge()) {
        mixer2grain3.gain(0, 1);
        float msec3 = 50.0 + (knobA14 * 130.0);
        granular3.beginFreeze(msec3);
        Serial.print("Begin granular freeze using ");
        Serial.print(msec3);
        Serial.println(" grains");
      }
      if (buttonGrain3.risingEdge()) {
        mixer2grain3.gain(0, 0);
        granular3.stop();
      }
      
      float ratio3;
      //ratio2 = powf(2.0, knobA10 * 2.0 - 1.0); // 0.5 to 2.0
      ratio3 = powf(2.0, knobA10 * 6.0 - 3.0); // 0.125 to 8.0 -- uncomment for far too much range!
      granular3.setSpeed(ratio3);
    
    }
                 
            
    
    void reverb() {
    
      //float KnobA10 = (float)analogRead(A10) / 1023.0;
    
      freeverbs1.roomsize(0.8);
      freeverbs1.damping(1);
    
      //revSend1.gain(0, KnobA10);
      //revSend1.gain(1, KnobA10);
      //revSend1.gain(2, KnobA10);
    
    }
    
    void envelope() {
    
      if (buttonPlay.fallingEdge()) {
        envelope1.noteOn();
      }
    
      if (buttonPlay.risingEdge()) {
        envelope1.noteOff();
      }
    
      envelope1.attack(50);
      envelope1.hold(10000);
      envelope1.release(11000);
      envelope1.sustain(1);
    
    }
    
    void vca1(){
    
      float fq1 = analogRead(A14) / 20;
      changeWaveform.update();
       
      waveform1.amplitude(1);
      waveform1.frequency(fq1 + 0.1);
      dc1.amplitude(1);
      
    
      if (changeWaveform.fallingEdge()){
        switch (current_waveform) {
          case WAVEFORM_SINE:
            current_waveform = WAVEFORM_SQUARE;
            Serial.println("Square");
            break;
          case WAVEFORM_SQUARE:
            current_waveform = WAVEFORM_SAMPLE_HOLD;
            Serial.println("S&H");
            break;
          case WAVEFORM_SAMPLE_HOLD:
            current_waveform = WAVEFORM_SINE;
            Serial.println("Sine");
            break;
        }
        AudioNoInterrupts();
        waveform1.begin(current_waveform);
        AudioInterrupts();
      }
    
    }

  2. #2
    Anyone has any hints on what to try here? =)

  3. #3
    Senior Member CorBee's Avatar
    Join Date
    Jun 2018
    Location
    Netherlands
    Posts
    527

  4. #4
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,870
    Assuming this is a Teensy 4.x?
    Is the latest TeensyDuino 1.54 in use?

  5. #5
    Quote Originally Posted by defragster View Post
    Assuming this is a Teensy 4.x?
    Is the latest TeensyDuino 1.54 in use?
    Yes! It is the Teensy 4.1 and I have the latest TeensyDuino running.

  6. #6
    Junior Member robz's Avatar
    Join Date
    Jul 2020
    Location
    Virginia, USA
    Posts
    5
    I'm not sure if this is helpful, but if you save the project as a new file name it will re-compile everything, that seems to have helped me from time to time.

    There seems to be almost the same effect of causing a complete re-compile by changing the cpu speed as well.

    Also, you may want to try upping the amount of AudioMemory.

    Good luck!

  7. #7
    Thanks for your replys! I could not solve the problem yet... I just added a blink without delay code to my program to check if the LED keeps blinking while I am recording a new loop. So this is working actually. The only thing that stopps is the granular freeze. Must be something with the memory I guess that I donīt understand yet. I also tried to remove the stopPlaying() from the startRecording(). With the effect that it actually still plays a short part of the loop that I then overwrite with the recording. Although a bit glichty... I guess it is what still is in the memory. So I need to tell the teensy somehow that it still should read the granular freezing from the memory while I am recording or so? I think the granular object is recording into the memory?

  8. #8
    Senior Member CorBee's Avatar
    Join Date
    Jun 2018
    Location
    Netherlands
    Posts
    527
    Try and see what happens if you disable your analogRead on A13. I think A13 is on ADC1 on the Teensy 4.1 and audio also uses ADC1 for its input. And its know that this leads to problems.
    But I have not tried your setup myself.

    Cor

  9. #9
    Hmm the A13 on the teensy 4.1 is on a pin that is not connected to the Audio Shield. In the beginning I had a problem using Digital Out 13 which is also connected to the onboard LED (I guess). But at the moment all my Pots are going in to inputs that are not used by the Audio Shield.

  10. #10
    Senior Member CorBee's Avatar
    Join Date
    Jun 2018
    Location
    Netherlands
    Posts
    527
    This ADC problem between Analogread and audio isnt related to a specific pin according to me, it has to do with ADC1 (and for ealier teensies ADC0). If you use that ADC also for analogread (so any of the ADC1 pins) this gives issues. Thats why I send you earlier a link ...

  11. #11
    Okay I see what happens. So I just use another Analog In for A13 and donīt use that one?

  12. #12
    So when I leave A13 unused and use A14 instead its still the same.

  13. #13
    Senior Member CorBee's Avatar
    Join Date
    Jun 2018
    Location
    Netherlands
    Posts
    527
    I suggested to test without any analogread, if it then works you know analogread is part of the problem. If it doesnt play it cant be due to analogread. If it works, you need to select an ADC from ADC2 on the teensy, the link above shows how to find those.

  14. #14
    Okay will try that out. Also just added a reverb after everything and you can still hear the reverb fading out when everything stops while I am pressing the record button. But lets see what happens without any Analog Ins...

  15. #15
    Still the same thing with no Analog Inputs used.

  16. #16
    Senior Member CorBee's Avatar
    Join Date
    Jun 2018
    Location
    Netherlands
    Posts
    527
    Thats unfortunate, I have no idea what breaks your code. You could try and add serial.print commands to see where in the flow it stops.

  17. #17
    Hmm yes its weird... Otherwise the rest works pretty well =)
    https://youtu.be/t2BSP8XRjSA

    Hopefully I will find a solution...

  18. #18
    I did a little bit more testing and used a delay object after the granular freezing. And when I record a new loop and the granular freezing stops, I can still hear the delay fadeing out. So it seems that the recording is stopping from reading the grain buffer but it still reads the delay buffer. So what can I do with this information? I can make a weird work around by opening the delay when the recording starts and set the delay time to the grain size with a feedback? Hmm...

  19. #19
    Senior Member CorBee's Avatar
    Join Date
    Jun 2018
    Location
    Netherlands
    Posts
    527
    You could see what AudioMemoryUsageMax() tells you on the reserved audiomemory. You have reserved 240 blocks I see but use very large granular buffers, maybe some blocks do not get cleared properly. This simple metric can be helpfull.

  20. #20
    The AudioMemryUsage goes up to 207

  21. #21
    Senior Member CorBee's Avatar
    Join Date
    Jun 2018
    Location
    Netherlands
    Posts
    527
    So thats also not the issue ... most of the audio objects run behind the screens in interrupts so they are a bit tricky to debug.

  22. #22
    Maybe it has to do with the structure of the granular object? Just wondering if instead of using the granular object for freezing I could just write a little code that just records directly into the memory and repeats that memory?

  23. #23
    I am wondering if this could matter. In that Granular Freezing part of the code here:

    Code:
    void grain2() {
      buttonGrain2.update();
    
      float FaderFreeze1Pitch = mux1.read(2) / 1023.0;
      //float FaderFreeze1Length = mux1.read(3) / 1023.0;
    
      // Button 0 starts Freeze effect
      if (buttonGrain2.fallingEdge()) {
        mixer2grain2.gain(0, 1);
     //   float msec2 = 50.0 + (FaderFreeze1Length * 150.0);
        float msec2 = 400;    
        granular2.beginFreeze(msec2);
        Serial.print("Begin granular freeze using ");
        Serial.print(msec2);
        Serial.println(" grains");
        Serial.println(millis());
      }
      if (buttonGrain2.risingEdge()) {
        mixer2grain2.gain(0, 0);
        granular2.stop();
      }
    
      float ratio2;
      //ratio2 = powf(2.0, knobA10 * 2.0 - 1.0); // 0.5 to 2.0
      ratio2 = powf(2.0, FaderFreeze1Pitch * 2.0 - 1.0); // 0.125 to 8.0 -- uncomment for far too much range!
      granular2.setSpeed(ratio2);
    
    }
    I added this
    Code:
     Serial.println(millis());
    The Idea was that I wanted to see if the ms are still printed to the Serial Monitor when I start to record a new Loop. I assumed to see that the monitor is printing the rising ms over and over again but instead I just get one number once the granular freezing button is on the falling edge. Could it be that there is a correlation between that and the stopping when the record button is on the falling edge?

  24. #24
    Senior Member CorBee's Avatar
    Join Date
    Jun 2018
    Location
    Netherlands
    Posts
    527
    But that serial.println(millis()) is inside a buttonstate it seems. You have 8ms debounce on those, maybe a bit short ?

  25. #25
    Now that I put it to the beginning of the void grain2() it counts the ms also during the recording mode. And I tried with 3ms debounce but still... it stays mysterious to me. Maybe just a limit of the granular object.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •