Skip to content

Commit

Permalink
Merge pull request #396 from shorepine/audioin
Browse files Browse the repository at this point in the history
Tulip changes for AMY memorypcm and audio-in
  • Loading branch information
bwhitman authored Oct 15, 2024
2 parents 57a1bad + a4e19c7 commit 7146245
Show file tree
Hide file tree
Showing 15 changed files with 76 additions and 830 deletions.
12 changes: 7 additions & 5 deletions docs/music.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,24 +305,26 @@ s.note_on(40, 1.0)
s.note_off(40) # looped instruments require a note_off to stop
```

You can load your own samples into Tulip. Take any .wav file and [load it onto Tulip.](getting_started.md#transfer-files-between-tulip-and-your-computer) Now, load it in as a `CUSTOM` PCM patch:
You can load your own samples into Tulip. Take any .wav file and [load it onto Tulip.](getting_started.md#transfer-files-between-tulip-and-your-computer) Now, load it in as a PCM patch:

```python
patch = tulip.load_sample('sample.wav')
s = midi.OscSynth(wave=amy.CUSTOM, patch=patch)
amy.load_sample('sample.wav', patch=50)
s = midi.OscSynth(wave=amy.PCM, patch=50)
s.note_on(60, 1.0)
```

You can also load PCM patches with looped segments if you have their loopstart and loopend parameters (these are often stored in the WAVE metadata. If the .wav file has this metadata, we'll parse it. The example file `/sys/ex/vlng3.wav` has it. You can also provide the metadata directly.) To indicate looping, use `feedback=1`.

```python
patch = tulip.load_sample("/sys/ex/vlng3.wav") # loads wave looping metadata
s = midi.OscSynth(wave=amy.CUSTOM, patch=patch, feedback=1, num_voices=1)
amy.load_sample("/sys/ex/vlng3.wav", patch=50) # loads wave looping metadata
s = midi.OscSynth(wave=amy.CUSTOM, patch=50, feedback=1, num_voices=1)
s.note_on(60, 1.0) # loops
s.note_on(55, 1.0) # loops
s.note_off(55) # stops
```

You can unload samples from RAM with `amy.unload_sample(patch_number)`.

## Modify Juno-6 patches programatically

We showed above how to `run('juno6')` to see a Juno-6 editor. But if you want your code to change the patches, you can do it yourself with:
Expand Down
24 changes: 15 additions & 9 deletions docs/tulip_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,26 +439,32 @@ amy.send(voices='0', load_patch=101, note=50, vel=1, client=2) # just a certain
alles.local() # turns off mesh mode and goes back to local mode
```

To load your own WAVE files as samples, use `tulip.load_sample`:
To load your own WAVE files as samples, use `amy.load_sample`:

```python
# To save space / RAM, you may want to downsample your WAVE files to 11025 or 22050Hz. We detect SR automatically.
patch = tulip.load_sample("flutea4.wav") # samples are converted to mono if they are stereo
amy.load_sample("flutea4.wav", patch=50) # samples are converted to mono if they are stereo. patch # can be anything

# You can optionally tell us the loop start and end point (in samples), and base MIDI note of the sample.
# We can detect this in WAVE file metadata if it exists! (Many sample packs include this.)
patch = tulip.load_sample("flutea4.wav", midinote=81, loopstart=1020, loopend=1500)
amy.load_sample("flutea4.wav", midinote=81, loopstart=1020, loopend=1500, patch=50)

# The patch number can now be used in the custom Tulip memory PCM sample player.
# It has all the features of the AMY's PCM wave type.
amy.send(osc=20, wave=amy.CUSTOM, patch=patch, vel=1, note=50)
# The patch number can now be used in AMY's PCM sample player.
amy.send(osc=20, wave=amy.PCM, patch=50, vel=1, note=50)

# You can load up to 32 custom PCM patches. Be careful of memory use. load_sample will return -1 if there's no more room.
# You can unload already allocated patches:
tulip.unload_patch(patch) # frees the RAM and the patch slot
tulip.unload_patch() # frees all allocated PCM patches
amy.unload_sample(patch) # frees the RAM and the patch slot
amy.reset() # frees all allocated PCM patches
```

On Tulip Desktop, or with an [AMYboard / AMYchip](https://github.com/shorepine/amychip) connected to a hardware Tulip over I2C, you can use audio input as well. This is brand new and we're still working out a good API for it. For now, you can set any oscillator to be fed by the L or R channel of an audio input.

```python
amy.send(osc=0, wave=amy.AUDIO_IN0, vel=1)
amy.echo(1, 250, 500, 0.8) # echo effect on the audio input
```


To send signals over CV on Tulip CC (hardware only):

```python
Expand Down
1 change: 1 addition & 0 deletions tulip/esp32s3/boards/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@
freeze("$(PORT_DIR)/../shared/py")
freeze("$(MPY_DIR)/../amy", "amy.py")
freeze("$(MPY_DIR)/../amy", "juno.py")
freeze("$(MPY_DIR)/../amy", "amy_wave.py")
#freeze("$(MPY_DIR)/lib/micropython-lib/micropython/utarfile", "utarfile.py")
2 changes: 1 addition & 1 deletion tulip/esp32s3/esp32_common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ list(APPEND MICROPY_SOURCE_EXTMOD
${TULIP_SHARED_DIR}/lvgl_u8g2.c
${TULIP_SHARED_DIR}/u8fontdata.c
${TULIP_SHARED_DIR}/u8g2_fonts.c
${TULIP_SHARED_DIR}/memorypcm.c
${AMY_DIR}/src/dsps_biquad_f32_ae32.S
${AMY_DIR}/src/algorithms.c
${AMY_DIR}/src/custom.c
Expand All @@ -194,6 +193,7 @@ list(APPEND MICROPY_SOURCE_EXTMOD
${AMY_DIR}/src/envelope.c
${AMY_DIR}/src/filters.c
${AMY_DIR}/src/oscillators.c
${AMY_DIR}/src/transfer.c
${AMY_DIR}/src/partials.c
${AMY_DIR}/src/pcm.c
${AMY_DIR}/src/log2_exp2.c
Expand Down
12 changes: 8 additions & 4 deletions tulip/linux/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,9 @@ static void sys_set_excecutable(char *argv0) {
#endif




extern int16_t amy_device_id;
extern int16_t amy_playback_device_id;
extern int16_t amy_capture_device_id;
extern void setup_lvgl();

/*
MP_NOINLINE int main_(int argc, char **argv);
Expand Down Expand Up @@ -875,7 +875,10 @@ int main(int argc, char **argv) {
switch(opt)
{
case 'd':
amy_device_id = atoi(optarg);
amy_playback_device_id = atoi(optarg);
break;
case 'c':
amy_capture_device_id = atoi(optarg);
break;
case 'l':
amy_print_devices();
Expand All @@ -884,6 +887,7 @@ int main(int argc, char **argv) {
case 'h':
fprintf(stderr,"usage: tulip\n");
fprintf(stderr,"\t[-d sound device id, use -l to list, default, autodetect]\n");
fprintf(stderr,"\t[-c capture sound device id, use -l to list, default, autodetect]\n");
fprintf(stderr,"\t[-l list all sound devices and exit]\n");
fprintf(stderr,"\t[-h show this help and exit]\n");
exit(0);
Expand Down
1 change: 1 addition & 0 deletions tulip/linux/variants/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
freeze("$(MPY_DIR)/../tulip/shared/py")
freeze("$(MPY_DIR)/../amy", "amy.py")
freeze("$(MPY_DIR)/../amy", "juno.py")
freeze("$(MPY_DIR)/../amy", "amy_wave.py")

9 changes: 7 additions & 2 deletions tulip/macos/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,8 @@ char * get_tulip_home_path() {
}


extern int16_t amy_device_id;
extern int16_t amy_playback_device_id;
extern int16_t amy_capture_device_id;

/*
MP_NOINLINE int main_(int argc, char **argv);
Expand Down Expand Up @@ -907,7 +908,10 @@ int main(int argc, char **argv) {
switch(opt)
{
case 'd':
amy_device_id = atoi(optarg);
amy_playback_device_id = atoi(optarg);
break;
case 'c':
amy_capture_device_id = atoi(optarg);
break;
case 'l':
amy_print_devices();
Expand All @@ -916,6 +920,7 @@ int main(int argc, char **argv) {
case 'h':
fprintf(stderr,"usage: tulip\n");
fprintf(stderr,"\t[-d sound device id, use -l to list, default, autodetect]\n");
fprintf(stderr,"\t[-c capture sound device id, use -l to list, default, autodetect]\n");
fprintf(stderr,"\t[-l list all sound devices and exit]\n");
fprintf(stderr,"\t[-h show this help and exit]\n");
exit(0);
Expand Down
1 change: 1 addition & 0 deletions tulip/macos/variants/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
freeze("$(MPY_DIR)/../tulip/shared/py")
freeze("$(MPY_DIR)/../amy", "amy.py")
freeze("$(MPY_DIR)/../amy", "juno.py")
freeze("$(MPY_DIR)/../amy", "amy_wave.py")

63 changes: 33 additions & 30 deletions tulip/shared/alles.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ int32_t clocks[255];
int32_t ping_times[255];
uint8_t alive = 1;

extern int32_t computed_delta ; // can be negative no prob, but usually host is larger # than client
extern uint8_t computed_delta_set ; // have we set a delta yet?
//extern int32_t computed_delta ; // can be negative no prob, but usually host is larger # than client
//extern uint8_t computed_delta_set ; // have we set a delta yet?
extern int32_t last_ping_time;

amy_err_t sync_init() {
Expand Down Expand Up @@ -90,9 +90,9 @@ void esp_fill_audio_buffer_task() {
// We turn off writing to i2s on r10 when doing on chip debugging because of pins
#ifndef TULIP_R10_DEBUG
size_t written = 0;
i2s_channel_write(tx_handle, block, AMY_BLOCK_SIZE * BYTES_PER_SAMPLE * AMY_NCHANS, &written, portMAX_DELAY);
if(written != AMY_BLOCK_SIZE * BYTES_PER_SAMPLE * AMY_NCHANS) {
fprintf(stderr,"i2s underrun: %d vs %d\n", written, AMY_BLOCK_SIZE * BYTES_PER_SAMPLE * AMY_NCHANS);
i2s_channel_write(tx_handle, block, AMY_BLOCK_SIZE * AMY_BYTES_PER_SAMPLE * AMY_NCHANS, &written, portMAX_DELAY);
if(written != AMY_BLOCK_SIZE * AMY_BYTES_PER_SAMPLE * AMY_NCHANS) {
fprintf(stderr,"i2s underrun: %d vs %d\n", written, AMY_BLOCK_SIZE * AMY_BYTES_PER_SAMPLE * AMY_NCHANS);
}
#endif

Expand Down Expand Up @@ -184,8 +184,6 @@ amy_err_t setup_i2s(void) {
#endif


extern struct custom_oscillator memorypcm;
extern void memorypcm_init();

#ifdef ESP_PLATFORM
#include "driver/i2c.h"
Expand Down Expand Up @@ -226,7 +224,6 @@ void run_alles() {


esp_amy_init();
amy_set_custom(&memorypcm);
amy_reset_oscs();
// Schedule a "turning on" sound
bleep();
Expand All @@ -243,8 +240,6 @@ void * alles_start(void *vargs) {
alles_local_ip = malloc(256);
alles_local_ip[0] = 0;
unix_amy_init();
amy_set_custom(&memorypcm);
memorypcm_init();
amy_reset_oscs();
// Schedule a "turning on" sound
// We don't do this by default on tulip desktop as all threads start at once and it makes the bleep sound bad
Expand Down Expand Up @@ -307,26 +302,30 @@ void alles_parse_message(char *message, uint16_t length) {
uint8_t ipv4 = 0;
uint16_t start = 0;
uint16_t c = 0;
//char local_message[MAX_RECEIVE_LEN];
//strncpy(local_message, message, length);
uint8_t sync_response = 0;

// Parse the AMY stuff out of the message first
struct event e = amy_parse_message(message);
uint8_t sync_response = 0;
//fprintf(stderr, "message is %s len is %d\n", message, length);
// Then pull out any alles-specific modes in this message
while(c < length+1) {
uint8_t b = message[c];
if(b == '_' && c==0) sync_response = 1;
if( ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')) || b == 0) { // new mode or end
if(mode=='g') client = atoi(message + start);
if(mode=='U') sync = atol(message + start);
if(mode=='W') external_map[e.osc] = atoi(message+start);
if(sync_response) if(mode=='r') ipv4=atoi(message + start);
if(sync_response) if(mode=='i') sync_index = atoi(message + start);
mode = b;
start = c + 1;
}
c++;
if(e.status == EVENT_TRANSFER_DATA) {
// transfer data already dealt with. we skip this followon check.
length = 0;
} else {
//fprintf(stderr, "message is %s len is %d\n", message, length);
// Then pull out any alles-specific modes in this message
while(c < length+1) {
uint8_t b = message[c];
if(b == '_' && c==0) sync_response = 1;
if( ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')) || b == 0) { // new mode or end
if(mode=='g') client = atoi(message + start);
if(mode=='U') sync = atol(message + start);
if(mode=='W') external_map[e.osc] = atoi(message+start);
if(sync_response) if(mode=='r') ipv4=atoi(message + start);
if(sync_response) if(mode=='i') sync_index = atoi(message + start);
mode = b;
start = c + 1;
}
c++;
}
}
if(sync_response) {
// If this is a sync response, let's update our local map of who is booted
Expand Down Expand Up @@ -423,8 +422,12 @@ void handle_sync(int32_t time, int8_t index) {
//mcast_send(message, strlen(message));
// Update computed delta (i could average these out, but I don't think that'll help too much)
//int64_t old_cd = computed_delta;
computed_delta = time - sysclock;
computed_delta_set = 1;

// TODO, fix this up for the new style
//computed_delta = time - sysclock;
//computed_delta_set = 1;


//if(old_cd != computed_delta) printf("Changed computed_delta from %lld to %lld on sync\n", old_cd, computed_delta);
}

Expand Down
Loading

0 comments on commit 7146245

Please sign in to comment.