MIDI Sequencer

Status
Not open for further replies.

mtiger

Well-known member
Hi all,

Been looking around for some information relating to writing some code for a MIDI sequencer, and have come across some pieces however nothing in particular in regards to how it would work and how to set up, or even write the logic required.

Does anyone have any ideas or could point me in the right direction of where to start? Anyone developed something similar before?

Cheers
M
 
The MIDI part is easy... the sequencing part is potentially very complex.

First step would be to decide how you interface should work.... knobs, sliders, button matrix?

This library looks interesting and might make your project a little more easily achievable:

https://github.com/adafruit/FifteenStep

It appears to support shuffle, arbitrary loop lengths, dynamic tempo, and MIDI clock.
 
Thanks for that Oddson. I have looked at that library which seems rather detailed, however am unable to even compile the 'Neo_mpr121' example due to the following not being declared:

MIDIEvent event = {command, combined, arg1, arg2};
MIDIUSB.write(event);
MIDIUSB.flush();
 
I'm not great with library dependencies so I'm not sure how the midi part is meant to work in standard Ardunio but this is the part you need to change to Teensydunio's usbMIDI commands anyway.

I think the lack of a generic and public usbMIDI.send() command may complicate this... some conditional logic could translate to note-on and note-off commands or there may be a way to use the private and generic internal 'send' function from the usbMIDI library instead.

I'm away from my Teensy setup over the holidays so I can't really help with that right now.

An easier solution is to call note-on and -off directly instead of having an internal 'midi' function.

You would just replace the calling commands

Code:
midi(channel, 0x9, pitch[i], vel[i]);  // with usbMIDI.usbMIDI.sendNoteOn(pitch[i], vel[i], channel);
midi(channel, 0x8, pitch[i], vel[i]); //  with usbMIDI.usbMIDI.sendNoteOff(pitch[i], vel[i], channel);

And maybe start with the basic example... are you intending to mix with neopixels?
 
Hello!
First post here...thanks for a wonderful platform! Really, too much fun.
I am also trying to bring a sequencer into a simple synth sketch written with Audio Tools.
But first, I'm looking at the Fifteenstep library, basic example. I should probably get this working before bringing Fifteenstep into my synth.

I have the MIDI Library and hardware set up example working from here: https://www.pjrc.com/teensy/td_libs_MIDI.html
(what satisfaction to see those midi notes firing off)
So i know my serial Midi output is working and wired correctly , on Serial1.

Here's the basic example as-is:

Code:
// ---------------------------------------------------------------------------
//
// basic.ino
//
// A MIDI sequencer example using a standard MIDI cable and a push button
// attached to pin 4.
//
// Author: Todd Treece <todd@uniontownlabs.org>
// Copyright: (c) 2015 Adafruit Industries
// License: GNU GPLv3
//
// ---------------------------------------------------------------------------
#include "FifteenStep.h"

// sequencer init
FifteenStep seq = FifteenStep();

// save button state
int button_last = 0;

void setup() {

  // set MIDI baud
  Serial.begin(31250);

  // initialize digital pin 13 as an output
  pinMode(13, OUTPUT);

  // initialize digital pin 4 as an input for a button
  pinMode(4, INPUT);

  // start sequencer and set callbacks
  seq.begin();
  seq.setMidiHandler(midi);
  seq.setStepHandler(step);

}

void loop() {

  // read the state of the button
  int button = digitalRead(4);

  // check for button press or release and
  // send note on or off to seqencer if needed
  if(button == HIGH && button_last == LOW) {

    // button pressed. play middle C preview now
    midi(0x0, 0x9, 0x3C, 0x40);
    // store note in sequence
    seq.setNote(0x0, 0x3C, 0x40);

  } else if(button == LOW && button_last == HIGH) {

    // button released. send middle C note off preview now
    midi(0x0, 0x8, 0x3C, 0x0);
    // store note off in sequence
    seq.setNote(0x0, 0x3C, 0x0);

  }

  // save button state
  button_last = button;

  // this is needed to keep the sequencer
  // running. there are other methods for
  // start, stop, and pausing the steps
  seq.run();

}

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                         SEQUENCER CALLBACKS                               //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

// called when the step position changes. both the current
// position and last are passed to the callback
void step(int current, int last) {

  // blink on even steps
  if(current % 2 == 0)
    digitalWrite(13, HIGH);
  else
    digitalWrite(13, LOW);

}

// the callback that will be called by the sequencer when it needs
// to send midi commands. this specific callback is designed to be
// used with a standard midi cable.
//
// the following image will show you how your MIDI cable should
// be wired to the Arduino:
// http://arduino.cc/en/uploads/Tutorial/MIDI_bb.png
void midi(byte channel, byte command, byte arg1, byte arg2) {

  if(command < 128) {
    // shift over command
    command <<= 4;
    // add channel to the command
    command |= channel;
  }

  // send MIDI data
  Serial.write(command);
  Serial.write(arg1);
  Serial.write(arg2);

}


Here's what I've done to bring the MIDI hardware example in, adding those bits in. I also reversed the button states, as we're not using Bounce:


Code:
// ---------------------------------------------------------------------------
//
// basic.ino
//
// A MIDI sequencer example using a standard MIDI cable and a push button
// attached to pin 4- *changed to 5
//
// Author: Todd Treece <todd@uniontownlabs.org>
// Copyright: (c) 2015 Adafruit Industries
// License: GNU GPLv3
//
// ---------------------------------------------------------------------------
#include "FifteenStep.h"
#include <MIDI.h>

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI1);
//const int channel = 1;


// sequencer init
FifteenStep seq = FifteenStep();

// save button state
int button_last = 0;

void setup() {

  // set MIDI baud
  //Serial1.begin(31250);
  
MIDI1.begin();

  // initialize digital pin 2 as an LED output
  pinMode(2, OUTPUT);

  // initialize digital pin 5 as an input for a button
  pinMode(5, INPUT);

  // start sequencer and set callbacks
  seq.begin();
  seq.setMidiHandler(midi);
  seq.setStepHandler(step);

}

void loop() {

  // read the state of the button
  int button = digitalRead(5);

  // check for button press or release and
  // send note on or off to seqencer if needed
  if(button == LOW && button_last == HIGH) {

    // button pressed. play middle C preview now
    midipipe(0x0, 0x9, 0x3C, 0x40);
    //(channel, noteon, pitch, velocity)??
    
    // store note in sequence
    seq.setNote(0x0, 0x3C, 0x40);

  } else if(button == HIGH && button_last == LOW) {

    // button released. send middle C note off preview now
    midipipe(0x0, 0x8, 0x3C, 0x0);
    // store note off in sequence
    seq.setNote(0x0, 0x3C, 0x0);

  }

  // save button state
  button_last = button;

  // this is needed to keep the sequencer
  // running. there are other methods for
  // start, stop, and pausing the steps
  seq.run();

}

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                         SEQUENCER CALLBACKS                               //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

// called when the step position changes. both the current
// position and last are passed to the callback
void step(int current, int last) {

  // blink on even steps
  if(current % 2 == 0)
    digitalWrite(2, HIGH);
  else
    digitalWrite(2, LOW);

}

// the callback that will be called by the sequencer when it needs
// to send midi commands. this specific callback is designed to be
// used with a standard midi cable.
//
// the following image will show you how your MIDI cable should
// be wired to the Arduino:
// http://arduino.cc/en/uploads/Tutorial/MIDI_bb.png


// MIDIcallback *comments from Fifteenstep.h*
//
// This defines the MIDI callback function format that is required by the
// sequencer.
//
// Most of the time these arguments will represent the following:
//
// channel: midi channel
// command: note on or off (0x9 or 0x8)
// arg1: pitch value
// arg1: velocity value
//
// It's possible that there will be other types of MIDI messages sent
// to this callback in the future, so please check the command sent if
// you are doing something other than passing on the MIDI messages to
// a MIDI library.
//

void midi(byte channel, byte command, byte arg1, byte arg2) {

  if(command < 128) {
    // shift over command
    command <<= 4;
    // add channel to the command
    command |= channel;
  }


  // send MIDI data
  Serial1.write(command);
  Serial1.write(arg1);
  Serial1.write(arg2);

}



The first issue is it won't compile, seems to be a conflict between the midi callback used in the sketch and MIDI.h ?

Code:
Arduino: 1.8.13 (Mac OS X), TD: 1.53, Board: "Teensy 4.1, Serial + MIDI, 600 MHz, Faster, US English"


15BASIC_AGAIN:126: error: 'void midi(byte, byte, byte, byte)' redeclared as different kind of symbol
 void midi(byte channel, byte command, byte arg1, byte arg2) {
                                                           ^
In file included from /Applications/Teensyduino.app/Contents/Java/hardware/teensy/avr/libraries/MIDI/src/midi_Defs.h:30:0,
                 from /Applications/Teensyduino.app/Contents/Java/hardware/teensy/avr/libraries/MIDI/src/MIDI.h:30,
                 from /Users/IIIIIo/Documents/TEENSY/15BASIC_AGAIN/15BASIC_AGAIN.ino:14:
/Applications/Teensyduino.app/Contents/Java/hardware/teensy/avr/libraries/MIDI/src/midi_Namespace.h:31:66: note: previous declaration 'namespace midi { }'
 #define BEGIN_MIDI_NAMESPACE            namespace MIDI_NAMESPACE {
                                                                  ^
/Applications/Teensyduino.app/Contents/Java/hardware/teensy/avr/libraries/MIDI/src/midi_Namespace.h:36:1: note: in expansion of macro 'BEGIN_MIDI_NAMESPACE'
 BEGIN_MIDI_NAMESPACE
 ^
15BASIC_AGAIN: In function 'void setup()':
15BASIC_AGAIN:41: error: expected primary-expression before ')' token
   seq.setMidiHandler(midi);
                          ^
15BASIC_AGAIN: In function 'void loop()':
15BASIC_AGAIN:56: error: expected primary-expression before '(' token
     midi(0x0, 0x9, 0x3C, 0x40);
         ^
15BASIC_AGAIN:65: error: expected primary-expression before '(' token
     midi(0x0, 0x8, 0x3C, 0x0);
         ^
15BASIC_AGAIN: In function 'void midi(byte, byte, byte, byte)':
15BASIC_AGAIN:126: error: 'void midi(byte, byte, byte, byte)' redeclared as different kind of symbol
 void midi(byte channel, byte command, byte arg1, byte arg2) {
                                                           ^
In file included from /Applications/Teensyduino.app/Contents/Java/hardware/teensy/avr/libraries/MIDI/src/midi_Defs.h:30:0,
                 from /Applications/Teensyduino.app/Contents/Java/hardware/teensy/avr/libraries/MIDI/src/MIDI.h:30,
                 from /Users/IIIIIo/Documents/TEENSY/15BASIC_AGAIN/15BASIC_AGAIN.ino:14:
/Applications/Teensyduino.app/Contents/Java/hardware/teensy/avr/libraries/MIDI/src/midi_Namespace.h:31:66: note: previous declaration 'namespace midi { }'
 #define BEGIN_MIDI_NAMESPACE            namespace MIDI_NAMESPACE {
                                                                  ^
/Applications/Teensyduino.app/Contents/Java/hardware/teensy/avr/libraries/MIDI/src/midi_Namespace.h:36:1: note: in expansion of macro 'BEGIN_MIDI_NAMESPACE'
 BEGIN_MIDI_NAMESPACE
 ^
'void midi(byte, byte, byte, byte)' redeclared as different kind of symbol


This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.




So, wild guess, let's rename 'midi' to something else. I used 'midipipe' :

Code:
// ---------------------------------------------------------------------------
//
// basic.ino
//
// A MIDI sequencer example using a standard MIDI cable and a push button
// attached to pin 4.
//
// Author: Todd Treece <todd@uniontownlabs.org>
// Copyright: (c) 2015 Adafruit Industries
// License: GNU GPLv3
//
// ---------------------------------------------------------------------------
#include "FifteenStep.h"
#include <MIDI.h>

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI1);
//const int channel = 1;


// sequencer init
FifteenStep seq = FifteenStep();

// save button state
int button_last = 0;

void setup() {

  // set MIDI baud
  //Serial1.begin(31250);
  
MIDI1.begin();

  // initialize digital pin 2 as an LED output
  pinMode(2, OUTPUT);

  // initialize digital pin 5 as an input for a button
  pinMode(5, INPUT);

  // start sequencer and set callbacks
  seq.begin();
  seq.setMidiHandler(midipipe);   //changed midi to midipipe
  seq.setStepHandler(step);

}

void loop() {

  // read the state of the button
  int button = digitalRead(5);

  // check for button press or release and
  // send note on or off to seqencer if needed
  if(button == LOW && button_last == HIGH) {

    // button pressed. play middle C preview now
    midipipe(0x0, 0x9, 0x3C, 0x40);
    //(channel, noteon, pitch, velocity)??
    
    // store note in sequence
    seq.setNote(0x0, 0x3C, 0x40);

  } else if(button == HIGH && button_last == LOW) {

    // button released. send middle C note off preview now
    midipipe(0x0, 0x8, 0x3C, 0x0);
    // store note off in sequence
    seq.setNote(0x0, 0x3C, 0x0);

  }

  // save button state
  button_last = button;

  // this is needed to keep the sequencer
  // running. there are other methods for
  // start, stop, and pausing the steps
  seq.run();

}

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                         SEQUENCER CALLBACKS                               //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

// called when the step position changes. both the current
// position and last are passed to the callback
void step(int current, int last) {

  // blink on even steps
  if(current % 2 == 0)
    digitalWrite(2, HIGH);
  else
    digitalWrite(2, LOW);

}

// the callback that will be called by the sequencer when it needs
// to send midi commands. this specific callback is designed to be
// used with a standard midi cable.
//
// the following image will show you how your MIDI cable should
// be wired to the Arduino:
// http://arduino.cc/en/uploads/Tutorial/MIDI_bb.png


// MIDIcallback *comments copied from Fifteenstep.h*
//
// This defines the MIDI callback function format that is required by the
// sequencer.
//
// Most of the time these arguments will represent the following:
//
// channel: midi channel
// command: note on or off (0x9 or 0x8)
// arg1: pitch value
// arg1: velocity value
//
// It's possible that there will be other types of MIDI messages sent
// to this callback in the future, so please check the command sent if
// you are doing something other than passing on the MIDI messages to
// a MIDI library.
//

void midipipe(byte channel, byte command, byte arg1, byte arg2) {

  if(command < 128) {
    // shift over command
    command <<= 4;
    // add channel to the command
    command |= channel;
  }


  // send MIDI data
  Serial1.write(command);
  Serial1.write(arg1);
  Serial1.write(arg2);

}


Successfully compiles, a bit of progress! But the sketch isn't working as expected.
The LED is blinking, seems like a sequence is running somewhere, neat.
My midi monitor on my laptop is showing I'm getting clock (at 125bpm), and an endless string of note off messages for a C-2, at velocity 0.
Pressing my button doesn't send anything over Serial1.


Two theories:
My new friend midipipe is collecting bytes (note on or off, note value, velocity, and channel) meant to forward to the MIDI library I'm using to transmit actual MIDI data to Serial1. Maybe there's something amiss in the order expected and how its being printed to Serial1?

By changing 'midi' to 'midipipe', I've broken something inside Fifteenstep that's looking for 'midi', so nothing is getting out of the sequencer besides clock.

Can anyone help sort this out? I'm not opposed to using usbMIDI to see this example working, as long as I understand *why* it works:)

All the Best!
 
Thanks for bumping this old thread, not previously seen Fifteensteps. Looks interesting, neopixels? neotrellis? What?

Got me curious. Have not dug into what gives with the hardware yet but after a quick look at at Fifteensteps basic, got it to compile for T2++, Mega and T3.2, then threw in the Midi lib and of course it fell over like you say.

I think one would need to weed through Fifteensteps and re-name stuff so it does not conflict with the usual Teensy Midi library and aim to get it working as it ordinarily would with the Teensy Midi library included.

Then, in basic.ino looking at the last few lines:-

Code:
void midi(byte channel, byte command, byte arg1, byte arg2) {

  if(command < 128) {
    // shift over command
    command <<= 4;
    // add channel to the command
    command |= channel;
  }

  // send MIDI data
  Serial.write(command);
  Serial.write(arg1);
  Serial.write(arg2);

}

then think of how to translate the content of the variables in:-

Code:
void midi(byte channel, byte command, byte arg1, byte arg2)

into Teensy Midi library compatible format.
Make any sense?
 
That does make sense!
Before going the route of renaming, I tried again attempting to use usbMIDI instead of hardware serial. The usbMIDI functions built into Teensy work so dang well in every example I’ve tried. And there’s no conflicts with the sketch as-is.

The translation is where I seem to be getting lost...
If arg1 is note number,
and arg2 is velocity,
Isn’t command either noteon, or noteoff, plus whatever this is doing:

if(command < 128) {
// shift over command
command <<= 4;
// add channel to the command
command |= channel;
}

I wonder how that would all format into a usbMIDI message teensy is happy with?
 
What hardware is needed to get it up and running? Would like to play with it in the light of a new day.

Suggest fiddling with the function :-

Code:
void midi(byte channel, byte command, byte arg1, byte arg2)

to print channel,command,arg1,arg2 on the serial monitor and see what it's giving us.
 
For hardware, i'm just using the one led and one pushbutton, usbMIDI to a laptop.
The basic Fifteenstep example, as I understand, should sequence that one button's performance over 16 steps and send it back over midi.
Multiple capacitive buttons and neopixels can wait until i see one button working :)

I use this free midi monitoring software (almost daily) to see what midi is coming and going to and from my computers and hardware/modular synths.

https://www.snoize.com/MIDIMonitor/

Or, I just arm a vst in whichever DAW and see what happens with midi input. However, MIDIMonitor gives a good look at channel, note, velocity, clock...with filtering to drill down on messages we're looking for. A midi note of C-2 isn't going to be audible on any synth, MIDIMonitor helps see that clearly.
 
Uploaded it to a Teensy LC with a button on pin 4 (needed a pullup) looking at TX pin with a CRO which shows what looks like the same midi message repeated fairly rapidly and a different message is seen with a buttonpress.

Not seeing anything resembling a sequence of different notes so it has not earned a midi connection to any MidiOx or synth.

Next step: Dug into the example neo_mpr121 and after some sniffing found line 118 :-
Code:
seq.setNote(channel, pitch[i], vel[i]);
Looks like we're setting many notes with pitch and vel arrays

Next, in basic.ino line 51 :-
Code:
seq.setNote(0x0, 0x3C, 0x40);
We are only setting one note so that is what we have, a One Note sequencer. Seems to fit as it only has one led. Lets face it it ain't really a sequencer unless it's got a row of leds on it so it seems logical to hook up some Neos and a capsense breakout. Don't have either but am curious enough to get some. Ordering.

Will be up to ten days before taking it any further this end.
 
Curiosity bit. Uploaded to a T3.6 with Din midi out and no button or pullup on pin 4.
Midiox sees Timing Clock 248 followed by a NoteOff for C-1.

As for the button, treating it like a touch sense, while a finger is on it, Timing Clock, C-1 NoteOff, NoteOn with Vel of 64 and NoteOff messages are seen for C4. It behaves like a single-note sequencer and stops when you take your finger off.

I suspect the C-1 NoteOff is a spurious emission as Timing Clock (F8) is single byte and we're still sending whatever the contents of arg1 and arg2 so it looks like Fifteensteps Midi out function needs some tidying up.

A similar thing is likely to occur when we try to send Song Select which is a two byte message which raises the question, what happens with the un-used arg.

The basic version does not seem to send Song Select but I expect that when more is working some issue is likely to pop up.
 
Similar results here, clock and lots of Noteoff for C-1 in the basic example.
I also have a capacitive board on the way, maybe one of the newer Fifteenstep examples will work, or give more clues.
I might also try decoupling the sequencer and 'midi' callback from midi output altogether, and send straight to myNoteOn and oscPlay functions in my audiotool-created synth.
My end project is a sequenced synth voice coming from the teensy, not necessarily a midi-controller.

Or, maybe this isn't the library I'm looking for, although the features are certainly there.
 
Curiously, it seems FifteenSteps thinks of 0-15 for Midi channel number. Teensy usbMidi spits out on Ch 16 if told to send on channel=0 whereas the Din midi lib spits nothing.

Needed to resort to some name-changing library tweaks to get it to co-habit with the DinMidi library.

Inverting the button sense seems logical as you get corresponding NoteOns and NoteOffs for press/release.
Have yet to see evidence of it sequencing buttonpressed notes though.
 
Got 32 blinkenlights and touch buttons find all the modes, Midi clock is present and note messages are emitted when pads are touched, however just like in the basic example nothing seems to get recorded or if it does, it ain't getting replayed.

Currently running on an LC for a quick test but moving to a T3.2 after adding Din Midi I/O, level shifter and a few "creature comforts" namely, I don't like how the display handles command mode so an extra Neo and I2c socket for a 7 segment perhaps.

By the way, I've tossed any mods done to the library as none made anything work which didn't and neither did anything break.

Methinks it's time to register and ask questions on the Adafruit forum.
 
Hi @phi
Making sense of it. Here's the current state of play. Din Midi in via 47FX and output via Serial.write, 47FX Serial and Teensy usbMidi concurrently although have commented out Serial.Write pathway. Notes in the code.

OK, FifteenSteps is basically a looper and the video has it doing percussion which hides it's flaw of generating "stuck notes", which even if it didn't in it's current form, would more than likely do so if you change number of steps on the fly and does in fact, let more stuck notes loose when you do change it.

A few thoughts:-

Looking at it's Max Tempo of 240 BPM and there's gear here that will run at up to 900 BPM, went sniffing and noted that the Library uses millis() to derive Clock, which as I understand is suboptimal in that with the math and the Midi heartbeat, accuracy kind of isn't and some tricky divisions, truncated remainders and so on can come into play.

One place where this can be fun is thinking of Shuffle or Swing. Some manufacturers seem to think of 0 - 100 here and what is it? 96 ticks per note so here's an opportunity for truncated remainders or whatever to wreak havoc. I reckon a choice of fixed values like 1/8, 1/4, 1/6, 3/4 and so on makes more sense (musically perhaps?) and not bother with the in-betweens coz I like that old Roland to sing, not sound like a noisy machine shop and the bonus is we can probably bypass the dodgy math in the process.

So my theory is If the same kind of math figures where to quantize the next incoming note and mistakenly tries to put one where it already just has, then it just might stick it in the next available spot as well, or at least something along those lines. Don't forget, the Clock's math is dodgy to start with and that it was after all written for an Arduino Leonardo or Micro...

Interestingly, while writing this it's been "idling". Had patched two keyboards to it on different channels and those channels were then patched out to two separate synths both dialled without sustain and release but with some decay and both synths have synced delay effect added. Every so often, some dodgy math makes it trip over and sound badly out of sync and slowly over time, like two clocks ticking it drifts and eventually trips and stumbles and the process repeats. I bet that's dodgy math again and that same kind of math is responsible for the stuck notes.

Anyway, here's a zip of the current code. Note that I'm currently using IDE 1.8.13, TD 1.53 and V 1.2.1 of the FifteenStep library, got there tailchasing.
Cheers.
 

Attachments

  • 15StepT_32_3WayOutput.zip
    5.1 KB · Views: 94
Last edited:
Got FifteenSteps working and added Midi in hardware and support for Note and Control Change messages. Commented out listening to Capsense.

Chasing stuck notes, hacked the library to a silly Max tempo and number of Leds to allocate one step per Midi Clock tick and synced Midi reads to it and still get stuck notes which kinda supports my theory about dodgy math.

A Teensy synth deserves a better embeddable sequencer.

For a few thoughts:-

The downfall of sequencers that record both the NoteOn and NoteOff messages is that you end up in a mess trying to deal with the NoteOffs just for stop or pause, never mind if you want to select First step, Last step, forward and reverse completely on the fly. Why would you want any less?

Measuring the incoming Note duration in Midi clock ticks then including the x # of ticks value along with the rest of the note data gives a single "Note event instruction" to record with inbuilt duration.

Theoretically we can manipulate any note or sequence parameter completely on the fly and provided our playback handler unpacks the single message correctly and generates the NoteOff after x # of Clock ticks, we don't have a basket case if we want to Stop, Pause, Reverse step direction, Transpose or change Output Channel on the fly.

Thinking.....
 
Status
Not open for further replies.
Back
Top