Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
1.6.0-bacon10
Allow greater granular stretching using LPOF
Documentation in tips and tricks
1.6.0-bacon5
Auto-load last project on startup
* Application can now load the last project automatically on startup
Expand Down
2 changes: 1 addition & 1 deletion docs/wiki/What-is-LittlePiggyTracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ In oscillator modes, under 0x80 the feedback of specified length is added to the
- pingpong will start at "start" and bounce the loop between loop start and loop end.
- oscillator is a special mode where the loop selection (from loop start to loop end) is taken as oscillator data and automatically tuned. Experiment with different settings, do not forget 'root note' is your friend to tune the oscillator back in a useful range
- looper sync will automatically tune a loop so that it plays exactly 16 bars. Use the root note to play twice faster/slower
- slicer will cut the sample into "slices" amount of samples, mapped from C-2 (the lowest possible note) up to amount of slices. Example: slices == 4 will give you four slices mapped to C-2, C#-2, D-2, D#-2
- **slices:** divides the sample into equal slices, mapped chromatically from C-2 upward. Set to 1 to disable. Example: slices == 4 gives four slices on C-2, C#-2, D-2, D#-2. When slices > 1, the active loop mode applies within each slice — use "loop" for looped slices, "none" for one-shot.
- **start:** start point of the sample regardless of if loop is enabled; in hex
- **loop Start:** start point of the sample when loop is enabled; in hex
- **loop End:** end point of the sample; in hex. You can play samples backwards by setting the end value lower than the start!
Expand Down
41 changes: 41 additions & 0 deletions docs/wiki/tips_and_tricks.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,47 @@ Using this, you can assign up to 256 individual slices from C-2 (C minus two) up
Of course, you are not limited to drum loops. Just chop anything away !
The example project BETA uses Slice mode to chop up a dank AF sample by Basscarrier

### Granular timestretching with LPOF

You can use `LPOF` in a looping table to scroll a short loop window through a sample independently of playback pitch — a form of granular timestretching. Set a short loop on the instrument, then add a table that advances the loop position every tick:

```
00 LPOF xxxx ---- ----
01 HOP 0000 ---- ----
```

Each tick, the loop window shifts forward by `xxxx` samples. The note's pitch is still determined by the loop length and oscillator tuning; the *content* of the loop changes as it travels through the sample. This lets you play a sample at a different speed than its pitch — stretching or compressing its duration without affecting the note you hear.

**Sync to BPM (V_sync)**

To advance the window in lock-step with the song tempo, you want the total shift per beat to equal the sample length. With 6 ticks per row (default), the per-tick shift is:

```
V_sync = (fileSampleRate × 10) / BPM
```

This gives the `LPOF` value (in decimal; convert to hex to enter it) that moves the window in sync with your BPM.

**Reference table — V_sync values**

| BPM | 44100 Hz | 22050 Hz | 11025 Hz | 8000 Hz |
|-----|----------|----------|----------|---------|
| 90 | `1388` (5000) | `09C4` (2500) | `04E2` (1250) | `038E` (910) |
| 160 | `0AC4` (2756) | `0562` (1378) | `02B1` (689) | `01F4` (500) |
| 175 | `09D8` (2520) | `04EC` (1260) | `0276` (630) | `01C7` (455) |

To stretch to *half speed* (two bars to traverse), halve the V_sync value. To double speed, double it.

**Speeding up vs. slowing down**

- **Speeding up** (forward `LPOF` values `0001`–`7FFF`): The window travels through the sample faster than realtime. Once it reaches the end it clamps there — no artefacts.
- **Slowing down**: Use a smaller V_sync value. The loop replays more cycles of each grain, effectively stretching time. Works best with long source samples.
- **Backward scrolling**: Values `8001`–`FFFF` shift the loop window backward (two's complement: `FFFF` = −1, `FF00` = −256, etc.).

**Amen break example (160 BPM, 44100 Hz)**

The classic Amen break is ~136 BPM. To play it in sync at 160 BPM, use `LPOF 0CB4` (3252 samples/tick). The window advances ~18% faster than its natural rate, stretching it up to 160 BPM without retuning the sample.

#Autorun your Pig on GP2X

Let's face it, playing live is a busy affair, and god help you if you have to reboot the gp2x for whatever reason. Luckily the GP2X allows you to drop a simple text file in the SD card root that will automatically boot any program. Here's how to get a piggy running [midi](./dfs_midi_cable.md) to Autorun.
Expand Down
156 changes: 83 additions & 73 deletions sources/Application/Instruments/SampleInstrument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,35 +230,37 @@ bool SampleInstrument::Start(int channel,unsigned char midinote,bool cleanstart)
}*/
rp->reverse_=false ;

float driverRate=float(Audio::GetInstance()->GetSampleRate()) ;

switch (loopmode) {
case SILM_ONESHOT:
case SILM_LOOP:
case SILM_LOOP_PINGPONG:

// Compute speed factor
// if instrument sampled below 44.1Khz, should
// travel slower in sample

rp->rendFirst_ = start_->GetInt();
rp->position_= float(rp->rendFirst_);
rp->baseSpeed_=fl2fp(source_->GetSampleRate(rp->midiNote_)/driverRate) ;
rp->reverse_=(rp->rendLoopEnd_<rp->position_) ;

break ;

case SILM_OSC:
// case SILM_OSCFINE:
{

float freq=261.6255653006f ; // C3
/* if (loopmode==SILM_OSCFINE) {
freq=float(pow(2.0,-0.75))*440; // C3
}*/
int length=rp->rendLoopEnd_-rp->rendLoopStart_ ;
if (length==0) length=1 ;
if (length<0) {
float driverRate = float(Audio::GetInstance()->GetSampleRate());

switch (loopmode) {
case SILM_ONESHOT:
case SILM_LOOP:
case SILM_LOOP_PINGPONG:

// Compute speed factor
// if instrument sampled below 44.1Khz, should
// travel slower in sample

rp->rendFirst_ = start_->GetInt();
rp->position_ = float(rp->rendFirst_);
rp->baseSpeed_ =
fl2fp(source_->GetSampleRate(rp->midiNote_) / driverRate);
rp->reverse_ = (rp->rendLoopEnd_ < rp->position_);

break;

case SILM_OSC:
// case SILM_OSCFINE:
{

float freq = 261.6255653006f; // C3
/* if (loopmode==SILM_OSCFINE) {
freq=float(pow(2.0,-0.75))*440; // C3
}*/
int length = rp->rendLoopEnd_ - rp->rendLoopStart_;
if (length == 0)
length = 1;
if (length < 0) {
rp->reverse_=true ;
length=-length ;
} ;
Expand Down Expand Up @@ -301,17 +303,15 @@ bool SampleInstrument::Start(int channel,unsigned char midinote,bool cleanstart)
case SILM_LAST:
NAssert(0) ;
break ;
}

}


// Compute octave & note difference from root
// Compute octave & note difference from root

float fineTune=float(fineTune_->GetInt()-0x7F) ;
fineTune/=float(0x80) ;
int offset=midinote-rootNote ;
if (loopmode == SILM_SLICE) {
offset = rootNote-source_->GetRootNote(rp->midiNote_);
float fineTune = float(fineTune_->GetInt() - 0x7F);
fineTune /= float(0x80);
int offset = midinote - rootNote;
if (loopmode == SILM_SLICE) {
offset = rootNote - source_->GetRootNote(rp->midiNote_);
}
while (offset>127)
{
Expand Down Expand Up @@ -1097,41 +1097,51 @@ void SampleInstrument::ProcessCommand(int channel,FourCC cc,ushort value) {
if (!source_) return ;

switch(cc) {
case I_CMD_LPOF:

if (value>0x8000) {
value=0x10000-value ;
if (rp->rendLoopStart_>value) {
rp->rendLoopEnd_-=value ;
rp->rendLoopStart_-=value ;
}
} else {
if (rp->rendLoopEnd_+value<source_->GetSize(rp->midiNote_)) {
rp->rendLoopEnd_+=value ;
rp->rendLoopStart_+=value ;
} ;
}
break ;

case I_CMD_PLOF:
{
if (!source_) return ;
int wavSize=source_->GetSize(rp->midiNote_) ;
float chkSize=wavSize/256.0f ;
int absShft=value>>8 ;
if (absShft!=0) {
rp->position_=chkSize*absShft ;
} ;
int relSfht=value&0xFF ;
if (relSfht>127) relSfht=relSfht-256 ;
rp->position_+=relSfht*chkSize ;
while (rp->position_<0) {
rp->position_=wavSize+rp->position_ ;
} ;
while (rp->position_>=wavSize) {
rp->position_-=wavSize;
} ;
rp->couldClick_=SHOULD_KILL_CLICKS ;
case I_CMD_LPOF:
if (value > 0x8000) {
int shift = (int)(0x10000 - value);
if (shift >
rp->rendLoopStart_) { // Clamp so to not back out of sample
shift = rp->rendLoopStart_;
}
rp->rendLoopEnd_ -= shift;
rp->rendLoopStart_ -= shift;
rp->position_ -= shift;
} else if (value > 0) { // LPOF 0000 is a no-op, this is not that
int sampleSize = source_->GetSize(rp->midiNote_);
int shift = (int)value;
if (rp->rendLoopEnd_ + shift >=
sampleSize) { // Clamp so to not play outside sample
shift = sampleSize - rp->rendLoopEnd_;
}
if (shift > 0) {
rp->rendLoopEnd_ += shift;
rp->rendLoopStart_ += shift;
rp->position_ += shift;
}
}
break;

case I_CMD_PLOF: {
if (!source_)
return;
int wavSize = source_->GetSize(rp->midiNote_);
float chkSize = wavSize / 256.0f;
int absShft = value >> 8;
if (absShft != 0) {
rp->position_ = chkSize * absShft;
};
int relSfht = value & 0xFF;
if (relSfht > 127)
relSfht = relSfht - 256;
rp->position_ += relSfht * chkSize;
while (rp->position_ < 0) {
rp->position_ = wavSize + rp->position_;
};
while (rp->position_ >= wavSize) {
rp->position_ -= wavSize;
};
rp->couldClick_ = SHOULD_KILL_CLICKS;
}
break ;

Expand Down
2 changes: 1 addition & 1 deletion sources/Application/Model/Project.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

#define PROJECT_NUMBER "1"
#define PROJECT_RELEASE "6"
#define BUILD_COUNT "0-bacon6"
#define BUILD_COUNT "0-bacon11"

#define MAX_TAP 3

Expand Down
Loading