pimoroni-pico/micropython/modules/stellar_unicorn/stellar_unicorn.cpp

517 wiersze
17 KiB
C++

#include "libraries/stellar_unicorn/stellar_unicorn.hpp"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "micropython/modules/util.hpp"
#include <cstdio>
#include <cfloat>
using namespace pimoroni;
extern "C" {
#include "stellar_unicorn.h"
#include "micropython/modules/pimoroni_i2c/pimoroni_i2c.h"
#include "py/builtin.h"
/********** Channel **********/
/***** Variables Struct *****/
typedef struct _Channel_obj_t {
mp_obj_base_t base;
AudioChannel* channel;
} _Channel_obj_t;
/***** Print *****/
void Channel_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind; //Unused input parameter
//_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
//AudioChannel* channel = self->channel;
mp_print_str(print, "Channel(");
mp_print_str(print, ")");
}
/***** Constructor *****/
mp_obj_t Channel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
mp_raise_msg(&mp_type_RuntimeError, "Cannot create Channel objects. They can only be accessed from StellarUnicorn.synth_channel()");
return mp_const_none;
}
/***** Destructor ******/
mp_obj_t Channel___del__(mp_obj_t self_in) {
return mp_const_none;
}
/***** Helper Functions *****/
void set_channel_waveforms(AudioChannel& channel, mp_obj_t in) {
int waveforms = mp_obj_get_int(in);
const int mask = (NOISE | SQUARE | SAW | TRIANGLE | SINE | WAVE);
if(waveforms < 0 || (waveforms & mask) == 0) {
mp_raise_ValueError("waveforms invalid. Expected a combination of NOISE, SQUARE, SAW, TRIANGLE, SINE, or WAVE");
}
channel.waveforms = (uint8_t)waveforms;
}
void set_channel_frequency(AudioChannel& channel, mp_obj_t in) {
int freq = mp_obj_get_int(in);
if(freq <= 0 || freq > UINT16_MAX) {
mp_raise_ValueError("frequency out of range. Expected greater than 0Hz to 65535Hz");
}
channel.frequency = (uint16_t)freq;
}
void set_channel_volume(AudioChannel& channel, mp_obj_t in) {
float volume = mp_obj_get_float(in);
if(volume < 0.0f || volume > 1.0f) {
mp_raise_ValueError("volume out of range. Expected 0.0 to 1.0");
}
channel.volume = (uint16_t)(volume * UINT16_MAX);
}
void set_channel_attack(AudioChannel& channel, mp_obj_t in) {
int attack_ms = (int)(mp_obj_get_float(in) * 1000.0f);
if(attack_ms < 0 || attack_ms > UINT16_MAX) {
mp_raise_ValueError("attack out of range. Expected 0.0s to 65.5s");
}
channel.attack_ms = MAX(attack_ms, 1);
}
void set_channel_decay(AudioChannel& channel, mp_obj_t in) {
int decay_ms = (int)(mp_obj_get_float(in) * 1000.0f);
if(decay_ms < 0 || decay_ms > UINT16_MAX) {
mp_raise_ValueError("decay out of range. Expected 0.0s to 65.5s");
}
channel.decay_ms = MAX(decay_ms, 1);
}
void set_channel_sustain(AudioChannel& channel, mp_obj_t in) {
float sustain = mp_obj_get_float(in);
if(sustain < 0.0f || sustain > 1.0f) {
mp_raise_ValueError("sustain out of range. Expected 0.0 to 1.0");
}
channel.sustain = (uint16_t)(sustain * UINT16_MAX);
}
void set_channel_release(AudioChannel& channel, mp_obj_t in) {
int release_ms = (int)(mp_obj_get_float(in) * 1000.0f);
if(release_ms < 0 || release_ms > UINT16_MAX) {
mp_raise_ValueError("release out of range. Expected 0.0s to 65.5s");
}
channel.release_ms = MAX(release_ms, 1);
}
void set_channel_pulse_width(AudioChannel& channel, mp_obj_t in) {
float pulse_width = mp_obj_get_float(in);
if(pulse_width < 0.0f || pulse_width > 1.0f) {
mp_raise_ValueError("pulse_width out of range. Expected 0.0 to 1.0");
}
channel.pulse_width = (uint16_t)(pulse_width * UINT16_MAX);
}
/***** Methods *****/
mp_obj_t Channel_configure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_waveforms, ARG_frequency, ARG_volume, ARG_attack, ARG_decay, ARG_sustain, ARG_release, ARG_pulse_width };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_waveforms, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_frequency, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_volume, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_attack, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_decay, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_sustain, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_release, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_pulse_width, MP_ARG_OBJ, {.u_obj = mp_const_none} }
};
// Parse args.
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _Channel_obj_t);
mp_obj_t waveforms = args[ARG_waveforms].u_obj;
if(waveforms != mp_const_none) {
set_channel_waveforms(*self->channel, waveforms);
}
mp_obj_t frequency = args[ARG_frequency].u_obj;
if(frequency != mp_const_none) {
set_channel_frequency(*self->channel, frequency);
}
mp_obj_t volume = args[ARG_volume].u_obj;
if(volume != mp_const_none) {
set_channel_volume(*self->channel, volume);
}
mp_obj_t attack = args[ARG_attack].u_obj;
if(attack != mp_const_none) {
set_channel_attack(*self->channel, attack);
}
mp_obj_t decay = args[ARG_decay].u_obj;
if(decay != mp_const_none) {
set_channel_decay(*self->channel, decay);
}
mp_obj_t sustain = args[ARG_sustain].u_obj;
if(sustain != mp_const_none) {
set_channel_sustain(*self->channel, sustain);
}
mp_obj_t release = args[ARG_release].u_obj;
if(release != mp_const_none) {
set_channel_release(*self->channel, release);
}
mp_obj_t pulse_width = args[ARG_pulse_width].u_obj;
if(pulse_width != mp_const_none) {
set_channel_pulse_width(*self->channel, pulse_width);
}
return mp_const_none;
}
mp_obj_t Channel_restore(mp_obj_t self_in) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
self->channel->restore();
return mp_const_none;
}
mp_obj_t Channel_waveforms(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_int(self->channel->waveforms);
}
set_channel_waveforms(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_frequency(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_int(self->channel->frequency);
}
set_channel_frequency(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_volume(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->volume / UINT16_MAX);
}
set_channel_volume(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_attack_duration(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->attack_ms / 1000.0f);
}
set_channel_attack(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_decay_duration(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->decay_ms / 1000.0f);
}
set_channel_decay(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_sustain_level(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->sustain / UINT16_MAX);
}
set_channel_sustain(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_release_duration(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->release_ms / 1000.0f);
}
set_channel_release(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_pulse_width(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->pulse_width / 0xffff);
}
set_channel_pulse_width(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_trigger_attack(mp_obj_t self_in) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
self->channel->trigger_attack();
return mp_const_none;
}
mp_obj_t Channel_trigger_release(mp_obj_t self_in) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
self->channel->trigger_release();
return mp_const_none;
}
mp_obj_t Channel_play_tone(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_freq, ARG_volume, ARG_fade_in, ARG_fade_out };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_frequency, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_volume, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_attack, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_release, MP_ARG_OBJ, {.u_obj = mp_const_none} },
};
// Parse args.
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _Channel_obj_t);
set_channel_frequency(*self->channel, args[ARG_freq].u_obj);
mp_obj_t volume = args[ARG_volume].u_obj;
if(volume != mp_const_none) {
set_channel_volume(*self->channel, volume);
}
else {
self->channel->volume = UINT16_MAX;
}
mp_obj_t attack_ms = args[ARG_fade_in].u_obj;
if(attack_ms != mp_const_none) {
set_channel_attack(*self->channel, attack_ms);
}
else {
self->channel->attack_ms = 1;
}
mp_obj_t release_ms = args[ARG_fade_out].u_obj;
if(release_ms != mp_const_none) {
set_channel_release(*self->channel, release_ms);
}
else {
self->channel->release_ms = 1;
}
self->channel->waveforms = Waveform::SINE;
self->channel->decay_ms = 1;
self->channel->sustain = UINT16_MAX;
self->channel->trigger_attack();
return mp_const_none;
}
/********** StellarUnicorn **********/
/***** Variables Struct *****/
typedef struct _StellarUnicorn_obj_t {
mp_obj_base_t base;
StellarUnicorn* stellar;
} _StellarUnicorn_obj_t;
typedef struct _ModPicoGraphics_obj_t {
mp_obj_base_t base;
PicoGraphics *graphics;
DisplayDriver *display;
void *spritedata;
void *buffer;
_PimoroniI2C_obj_t *i2c;
//mp_obj_t scanline_callback; // Not really feasible in MicroPython
} ModPicoGraphics_obj_t;
/***** Print *****/
void StellarUnicorn_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind; //Unused input parameter
//_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
mp_print_str(print, "StellarUnicorn()");
}
/***** Constructor *****/
mp_obj_t StellarUnicorn_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
_StellarUnicorn_obj_t *self = nullptr;
enum { ARG_pio, ARG_sm, ARG_pins, ARG_common_pin, ARG_direction, ARG_counts_per_rev, ARG_count_microsteps, ARG_freq_divider };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_pio, MP_ARG_INT },
{ MP_QSTR_sm, MP_ARG_INT }
};
// Parse args.
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
int pio_int = args[ARG_pio].u_int;
if(pio_int < 0 || pio_int > (int)NUM_PIOS) {
mp_raise_ValueError("pio out of range. Expected 0 to 1");
}
//PIO pio = pio_int == 0 ? pio0 : pio1;
int sm = args[ARG_sm].u_int;
if(sm < 0 || sm > (int)NUM_PIO_STATE_MACHINES) {
mp_raise_ValueError("sm out of range. Expected 0 to 3");
}
StellarUnicorn *stellar = m_new_class(StellarUnicorn);
stellar->init();
self = m_new_obj_with_finaliser(_StellarUnicorn_obj_t);
self->base.type = &StellarUnicorn_type;
self->stellar = stellar;
return MP_OBJ_FROM_PTR(self);
}
/***** Destructor ******/
mp_obj_t StellarUnicorn___del__(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
m_del_class(StellarUnicorn, self->stellar);
return mp_const_none;
}
/***** Methods *****/
extern mp_obj_t StellarUnicorn_clear(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->clear();
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_update(mp_obj_t self_in, mp_obj_t graphics_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
ModPicoGraphics_obj_t *picographics = MP_OBJ_TO_PTR2(graphics_in, ModPicoGraphics_obj_t);
self->stellar->update(picographics->graphics);
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_set_brightness(mp_obj_t self_in, mp_obj_t value) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->set_brightness(mp_obj_get_float(value));
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_get_brightness(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
return mp_obj_new_float(self->stellar->get_brightness());
}
extern mp_obj_t StellarUnicorn_adjust_brightness(mp_obj_t self_in, mp_obj_t delta) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->adjust_brightness(mp_obj_get_float(delta));
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_set_volume(mp_obj_t self_in, mp_obj_t value) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->set_volume(mp_obj_get_float(value));
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_get_volume(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
return mp_obj_new_float(self->stellar->get_volume());
}
extern mp_obj_t StellarUnicorn_adjust_volume(mp_obj_t self_in, mp_obj_t delta) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->adjust_volume(mp_obj_get_float(delta));
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_light(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
return mp_obj_new_float(self->stellar->light());
}
extern mp_obj_t StellarUnicorn_is_pressed(mp_obj_t self_in, mp_obj_t button) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
return mp_obj_new_bool(self->stellar->is_pressed((uint8_t)mp_obj_get_int(button)));
}
extern mp_obj_t StellarUnicorn_play_sample(mp_obj_t self_in, mp_obj_t data) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_RW);
if(bufinfo.len < 1) {
mp_raise_ValueError("Supplied buffer is too small!");
}
self->stellar->play_sample((uint8_t *)bufinfo.buf, bufinfo.len);
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_play_synth(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->play_synth();
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_stop_playing(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->stop_playing();
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_synth_channel(mp_obj_t self_in, mp_obj_t channel_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
// Check that the channel is valid
int channel = mp_obj_get_int(channel_in);
if(channel < 0 || channel >= (int)PicoSynth::CHANNEL_COUNT) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("channel out of range. Expected 0 to %d"), PicoSynth::CHANNEL_COUNT - 1);
}
// NOTE This seems to work, in that it give MP access to the calibration object
// Could very easily mess up in weird ways once object deletion is considered?
_Channel_obj_t *channel_obj = m_new_obj_with_finaliser(_Channel_obj_t);
channel_obj->base.type = &Channel_type;
channel_obj->channel = &self->stellar->synth_channel(channel);
return MP_OBJ_FROM_PTR(channel_obj);
}
}