├── README.md ├── Models ├── cap.stl ├── inner 1.stl ├── inner 2.stl ├── outer 1.stl ├── tube cap.stl ├── lightsaber.blend ├── cut template 1.stl └── cut template 2.stl ├── POVLightsaber ├── luni.jpg ├── vader.pdn ├── vader.png ├── berthold.jpg ├── vadergray.png ├── vaderred.png ├── data │ ├── image0.bin │ ├── image1.bin │ ├── image2.bin │ └── image3.bin ├── electromaker.png ├── sfx │ ├── lightsaber on.wav │ └── lightsaber off.wav ├── SynthSound.h ├── File.h ├── WS2812BI2S.h ├── AudioOutput.h ├── Gyro.h ├── AudioSystem.h └── POVLightsaber.ino └── Converter └── Converter.html /README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Models/cap.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/Models/cap.stl -------------------------------------------------------------------------------- /Models/inner 1.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/Models/inner 1.stl -------------------------------------------------------------------------------- /Models/inner 2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/Models/inner 2.stl -------------------------------------------------------------------------------- /Models/outer 1.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/Models/outer 1.stl -------------------------------------------------------------------------------- /Models/tube cap.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/Models/tube cap.stl -------------------------------------------------------------------------------- /Models/lightsaber.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/Models/lightsaber.blend -------------------------------------------------------------------------------- /POVLightsaber/luni.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/luni.jpg -------------------------------------------------------------------------------- /POVLightsaber/vader.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/vader.pdn -------------------------------------------------------------------------------- /POVLightsaber/vader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/vader.png -------------------------------------------------------------------------------- /Models/cut template 1.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/Models/cut template 1.stl -------------------------------------------------------------------------------- /Models/cut template 2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/Models/cut template 2.stl -------------------------------------------------------------------------------- /POVLightsaber/berthold.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/berthold.jpg -------------------------------------------------------------------------------- /POVLightsaber/vadergray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/vadergray.png -------------------------------------------------------------------------------- /POVLightsaber/vaderred.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/vaderred.png -------------------------------------------------------------------------------- /POVLightsaber/data/image0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/data/image0.bin -------------------------------------------------------------------------------- /POVLightsaber/data/image1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/data/image1.bin -------------------------------------------------------------------------------- /POVLightsaber/data/image2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/data/image2.bin -------------------------------------------------------------------------------- /POVLightsaber/data/image3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/data/image3.bin -------------------------------------------------------------------------------- /POVLightsaber/electromaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/electromaker.png -------------------------------------------------------------------------------- /POVLightsaber/sfx/lightsaber on.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/sfx/lightsaber on.wav -------------------------------------------------------------------------------- /POVLightsaber/sfx/lightsaber off.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitluni/POVLightsaber/HEAD/POVLightsaber/sfx/lightsaber off.wav -------------------------------------------------------------------------------- /POVLightsaber/SynthSound.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class SynthSound: public SoundBase 4 | { 5 | public: 6 | unsigned int position[5]; 7 | int positionIncrement[5]; 8 | signed char sinTab[256]; 9 | int pitch; 10 | int volume; 11 | 12 | void init(AudioSystem &audioSystem) 13 | { 14 | static const int baseFreq[] = {74, 92, 278, 370, 462}; 15 | next = 0; 16 | id = 0; 17 | pitch = 256; 18 | for(int i = 0; i < 5; i++) 19 | { 20 | position[i] = 0; 21 | positionIncrement[i] = int((65536.0 * 256.0 * baseFreq[i]) / audioSystem.samplingRate); 22 | } 23 | this->volume = 1 * 256; 24 | playing = true; 25 | for(int i = 0; i < 256; i++) 26 | sinTab[i] = int(sin((i / 128.) * M_PI) * 127); 27 | } 28 | 29 | virtual int nextSample() 30 | { 31 | static const int baseVolume[] = {64, 170, 90, 50, 40}; 32 | int s = 0; 33 | for(int i = 0; i < 5; i++) 34 | { 35 | s += (sinTab[(position[i] >> 16) & 255] * baseVolume[i] * volume) >> 8; 36 | position[i] += (positionIncrement[i] * pitch) >> 8; 37 | } 38 | return s; 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /POVLightsaber/File.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "esp_spiffs.h" 4 | 5 | void initFileSystem() 6 | { 7 | esp_vfs_spiffs_conf_t conf = { 8 | .base_path = "/spiffs", 9 | .partition_label = NULL, 10 | .max_files = 16, 11 | .format_if_mount_failed = true 12 | }; 13 | esp_vfs_spiffs_register(&conf); 14 | //esp_spiffs_format(0); 15 | size_t total = 0, used = 0; 16 | esp_spiffs_info(0, &total, &used); 17 | Serial.print("Total space on spiffs "); 18 | Serial.println(total); 19 | Serial.print("Used space "); 20 | Serial.println(used); 21 | } 22 | 23 | void deinitFileSystem() 24 | { 25 | //esp_vfs_spiffs_unregister(0); 26 | } 27 | 28 | bool readFromFile(const char *fileName, unsigned char *dest, int count) 29 | { 30 | Serial.print("Reading "); 31 | Serial.print(count); 32 | Serial.println(" bytes from file."); 33 | FILE* f = fopen(fileName, "r"); 34 | if(!f) return false; 35 | fread(dest, sizeof(unsigned char), count, f); 36 | return true; 37 | } 38 | 39 | bool writeToFile(const char *fileName, unsigned char *src, int count) 40 | { 41 | Serial.print("Writing "); 42 | Serial.print(count); 43 | Serial.println(" bytes to file."); 44 | FILE* f = fopen(fileName, "w"); 45 | if(!f) return false; 46 | fwrite(src, sizeof(unsigned char), count, f); 47 | fclose(f); 48 | return true; 49 | } 50 | -------------------------------------------------------------------------------- /POVLightsaber/WS2812BI2S.h: -------------------------------------------------------------------------------- 1 | #include "driver/i2s.h" 2 | 3 | static const i2s_port_t i2s_num = (i2s_port_t)1; 4 | static const int pixelCount = 144 + 2; 5 | static const int samplesNeeded = (pixelCount * 24 * 4 + 31) / 32; 6 | static const int stereoSamplesNeeded = (samplesNeeded + 1) / 2; 7 | static const int bufferSize = 128; 8 | static const int bufferCount = (stereoSamplesNeeded + bufferSize - 1) / bufferSize; 9 | static const int allocatedSamples = bufferCount * bufferSize * 2; 10 | 11 | static unsigned int bitLUT[256]; 12 | 13 | static const i2s_config_t i2s_config = { 14 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), 15 | .sample_rate = 833333, 16 | .bits_per_sample = (i2s_bits_per_sample_t)32, 17 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT , 18 | .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), 19 | .intr_alloc_flags = 0, 20 | .dma_buf_count = bufferCount * 2, 21 | .dma_buf_len = bufferSize, 22 | .use_apll = false 23 | }; 24 | 25 | static const i2s_pin_config_t pin_config = { 26 | .bck_io_num = 12, 27 | .ws_io_num = 14, 28 | .data_out_num = 27, 29 | .data_in_num = I2S_PIN_NO_CHANGE 30 | }; 31 | 32 | unsigned long pixels[allocatedSamples]; 33 | 34 | void initPixels() 35 | { 36 | i2s_driver_install(i2s_num, &i2s_config, 0, NULL); 37 | i2s_set_pin(i2s_num, &pin_config); 38 | i2s_set_sample_rates(i2s_num, 833333); 39 | 40 | //this is the hack that enables the highest sampling rate possible ~13MHz, have fun 41 | SET_PERI_REG_BITS(I2S_CLKM_CONF_REG(1), I2S_CLKM_DIV_A_V, 1, I2S_CLKM_DIV_A_S); 42 | SET_PERI_REG_BITS(I2S_CLKM_CONF_REG(1), I2S_CLKM_DIV_B_V, 1, I2S_CLKM_DIV_B_S); 43 | SET_PERI_REG_BITS(I2S_CLKM_CONF_REG(1), I2S_CLKM_DIV_NUM_V, 18, I2S_CLKM_DIV_NUM_S); 44 | SET_PERI_REG_BITS(I2S_SAMPLE_RATE_CONF_REG(1), I2S_TX_BCK_DIV_NUM_V, 2, I2S_TX_BCK_DIV_NUM_S); 45 | 46 | for(int i = 0; i < allocatedSamples; i++) 47 | pixels[i] = 0; 48 | for(int i = 0; i < 256; i++) 49 | { 50 | bitLUT[i] = 0; 51 | for(int bit = 7; bit >= 0; bit--) 52 | bitLUT[i] |= (((i >> bit) & 1)?0b1110 : 0b1000) << (bit * 4); 53 | } 54 | } 55 | 56 | void loopPixels() 57 | { 58 | static int rgb = 0; 59 | rgb++; 60 | int sample = 0; 61 | for(int i = 0; i < pixelCount; i++) 62 | { 63 | pixels[sample++] = bitLUT[(rgb >> 8) & 255]; 64 | pixels[sample++] = bitLUT[rgb & 255]; 65 | pixels[sample++] = bitLUT[(rgb >> 16) & 255]; 66 | } 67 | i2s_write_bytes(i2s_num, (char*)pixels, allocatedSamples * 4, portMAX_DELAY); 68 | } 69 | -------------------------------------------------------------------------------- /Converter/Converter.html: -------------------------------------------------------------------------------- 1 | 2 | bitluni's POV lightsaber 3 | 59 | 78 | 79 | 80 |

bitluni's POV lightsaber

81 |
82 | 86 |
87 |
88 | 89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /POVLightsaber/AudioOutput.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class AudioOutput; 9 | void IRAM_ATTR timerInterrupt(AudioOutput *audioOutput); 10 | 11 | 12 | class AudioOutput 13 | { 14 | public: 15 | AudioSystem *audioSystem; 16 | int cleanRegDAC2; 17 | int cleanRegDAC1; 18 | 19 | void init(AudioSystem &audioSystem) 20 | { 21 | this->audioSystem = &audioSystem; 22 | timer_config_t config; 23 | config.alarm_en = 1; 24 | config.auto_reload = 1; 25 | config.counter_dir = TIMER_COUNT_UP; 26 | config.divider = 16; 27 | config.intr_type = TIMER_INTR_LEVEL; 28 | config.counter_en = TIMER_PAUSE; 29 | timer_init((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0, &config); 30 | timer_pause((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0); 31 | timer_set_counter_value((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0, 0x00000000ULL); 32 | timer_set_alarm_value((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0, 1.0/audioSystem.samplingRate * TIMER_BASE_CLK / config.divider); 33 | timer_enable_intr((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0); 34 | timer_isr_register((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0, (void (*)(void*))timerInterrupt, (void*) this, ESP_INTR_FLAG_IRAM, NULL); 35 | timer_start((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0); 36 | 37 | CLEAR_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_SW_TONE_EN); 38 | CLEAR_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN1_M); 39 | CLEAR_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN2_M); 40 | SET_PERI_REG_BITS(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_DAC, 255, RTC_IO_PDAC1_DAC_S); 41 | cleanRegDAC1 = (READ_PERI_REG(RTC_IO_PAD_DAC1_REG)&(~((RTC_IO_PDAC1_DAC)<<(RTC_IO_PDAC1_DAC_S)))); 42 | cleanRegDAC2 = (READ_PERI_REG(RTC_IO_PAD_DAC2_REG)&(~((RTC_IO_PDAC2_DAC)<<(RTC_IO_PDAC2_DAC_S)))); 43 | 44 | dac_output_enable((dac_channel_t)1); 45 | } 46 | 47 | void deinit() 48 | { 49 | timer_pause((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0); 50 | } 51 | 52 | }; 53 | 54 | void IRAM_ATTR timerInterrupt(AudioOutput *audioOutput) 55 | { 56 | uint32_t intStatus = TIMERG0.int_st_timers.val; 57 | if(intStatus & BIT(TIMER_0)) 58 | { 59 | TIMERG0.hw_timer[TIMER_0].update = 1; 60 | TIMERG0.int_clr_timers.t0 = 1; 61 | TIMERG0.hw_timer[TIMER_0].config.alarm_en = 1; 62 | dac_output_voltage((dac_channel_t)1, audioOutput->audioSystem->nextSample()); 63 | //WRITE_PERI_REG(RTC_IO_PAD_DAC2_REG, audioOutput->cleanRegDAC2 | ((audioOutput->audioSystem->nextSample() & RTC_IO_PDAC2_DAC) << RTC_IO_PDAC2_DAC_S)); 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /POVLightsaber/Gyro.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | const float accScale= 9.81f/ 16384; 4 | const float rotScale= 1.f / 131; 5 | 6 | class Gyro 7 | { 8 | public: 9 | uint8_t address; 10 | float positionA[3]; 11 | float rotationV[3]; 12 | int correctionR[3]; 13 | float temperature; 14 | int oldTime; 15 | int gscale, ascale; 16 | 17 | Gyro(int ascale = 0, int gscale = 0, uint8_t address = 0x68) 18 | { 19 | Wire.begin(); 20 | this->address = address; 21 | //wake up call 22 | wakeUp(); 23 | this->ascale = ascale; 24 | this->gscale = gscale; 25 | setScale(); 26 | correctionR[0] = correctionR[1] = correctionR[2] = 0; 27 | oldTime = 0; 28 | } 29 | 30 | void wakeUp() 31 | { 32 | Wire.beginTransmission(address); 33 | Wire.write(0x6B); 34 | Wire.write(0); 35 | Wire.endTransmission(true); 36 | } 37 | 38 | void setScale() 39 | { 40 | Wire.beginTransmission(address); 41 | Wire.write(0x1B); 42 | Wire.write(gscale << 3); 43 | Wire.endTransmission(true); 44 | Wire.beginTransmission(address); 45 | Wire.write(0x1C); 46 | Wire.write(ascale << 3); 47 | Wire.endTransmission(true); 48 | } 49 | 50 | inline signed short readShort() const 51 | { 52 | return (Wire.read() << 8) | Wire.read(); 53 | } 54 | 55 | void calculateCorrection(int samples = 100) 56 | { 57 | long dr[3] = {0, 0, 0}; 58 | for(int i = 0; i < samples; i++) 59 | { 60 | Wire.beginTransmission(address); 61 | Wire.write(0x3B); 62 | Wire.endTransmission(false); 63 | Wire.requestFrom((uint8_t)address, (uint8_t)14, (uint8_t)true); 64 | positionA[0] = readShort() * accScale * (1 << ascale); 65 | positionA[1] = readShort() * accScale * (1 << ascale); 66 | positionA[2] = readShort() * accScale * (1 << ascale); 67 | temperature = readShort() / 340.f + 36.53f; 68 | dr[0] += readShort(); 69 | dr[1] += readShort(); 70 | dr[2] += readShort(); 71 | delay(10); 72 | } 73 | correctionR[0] = dr[0] / samples; 74 | correctionR[1] = dr[0] / samples; 75 | correctionR[2] = dr[0] / samples; 76 | } 77 | 78 | int poll() 79 | { 80 | Wire.beginTransmission(address); 81 | Wire.write(0x3B); 82 | Wire.endTransmission(false); 83 | Wire.requestFrom((uint8_t)address, (uint8_t)14, (uint8_t)true); 84 | positionA[0] = readShort() * accScale * (1 << ascale); 85 | positionA[1] = readShort() * accScale * (1 << ascale); 86 | positionA[2] = readShort() * accScale * (1 << ascale); 87 | temperature = readShort() / 340.f + 36.53f; 88 | rotationV[0] = (readShort() - correctionR[0]) * rotScale * (1 << gscale); 89 | rotationV[1] = (readShort() - correctionR[1]) * rotScale * (1 << gscale); 90 | rotationV[2] = (readShort() - correctionR[2]) * rotScale * (1 << gscale); 91 | } 92 | }; 93 | 94 | 95 | -------------------------------------------------------------------------------- /POVLightsaber/AudioSystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | class SoundBase 3 | { 4 | public: 5 | bool playing; 6 | int id; 7 | SoundBase *next; 8 | 9 | void remove(SoundBase **prevNext) 10 | { 11 | *prevNext = next; 12 | delete this; 13 | } 14 | 15 | void insert(SoundBase **prevNext) 16 | { 17 | next = *prevNext; 18 | *prevNext = this; 19 | } 20 | 21 | ///returns the next rendered sample. 16bit since it's scaled by volume 22 | virtual int nextSample() = 0; 23 | }; 24 | 25 | class Sound: public SoundBase 26 | { 27 | public: 28 | const signed char *samples; 29 | int sampleCount; 30 | long long position; 31 | int positionIncrement; 32 | int volume; 33 | bool loop; 34 | 35 | void init(const signed char *samples, int sampleCount, float volume = 1, float speed = 1, bool loop = false) 36 | { 37 | next = 0; 38 | id = 0; 39 | this->samples = samples; 40 | this->sampleCount = sampleCount; 41 | this->loop = loop; 42 | position = 0; 43 | positionIncrement = int(65536 * speed); 44 | this->volume = volume * 256; 45 | playing = true; 46 | } 47 | 48 | ///returns the next rendered sample. 16bit since it's scaled by volume 49 | virtual int nextSample() 50 | { 51 | if(!playing) return 0; 52 | int s = samples[position >> 16] * volume; 53 | position += positionIncrement; 54 | if((position >> 16) >= sampleCount) 55 | { 56 | if(loop) 57 | position = 0; 58 | else 59 | playing = false; 60 | } 61 | return s; 62 | } 63 | }; 64 | 65 | class AudioSystem 66 | { 67 | public: 68 | SoundBase *sounds; 69 | int samplingRate; 70 | unsigned char *buffer; 71 | int bufferSize; 72 | bool swapped; 73 | int currentId; 74 | volatile int readPosition; 75 | int writePosition; 76 | int volume; 77 | 78 | AudioSystem(int samplingRate, int bufferSize) 79 | { 80 | this->samplingRate = samplingRate; 81 | this->bufferSize = bufferSize; 82 | sounds = 0; 83 | buffer = (unsigned char*)malloc(bufferSize * sizeof(unsigned char)); 84 | for(int i = 0; i < bufferSize; i++) 85 | buffer[i] = 128; 86 | swapped = true; 87 | currentId = 0; 88 | readPosition = 0; 89 | writePosition = 0; 90 | volume = 256; 91 | } 92 | 93 | ///plays a sound, and returns an idividual id 94 | int play(SoundBase *sound) 95 | { 96 | sound->id = currentId; 97 | sound->insert(&sounds); 98 | return currentId++; 99 | } 100 | 101 | ///fills the buffer with all saounds that are currently playing 102 | void calcSamples() 103 | { 104 | int samples = readPosition - writePosition; 105 | if(samples < 0) 106 | samples += bufferSize; 107 | for(int i = 0; i < samples; i++) 108 | { 109 | int sample = 0; 110 | SoundBase **soundp = &sounds; 111 | while(*soundp) 112 | { 113 | sample += (*soundp)->nextSample(); 114 | if(!(*soundp)->playing) 115 | (*soundp)->remove(soundp); 116 | else 117 | soundp = &(*soundp)->next; 118 | } 119 | if(sample >= (1 << 15)) 120 | sample = (1 << 15) - 1; 121 | else if(sample < -(1 << 15)) 122 | sample = -(1 << 15); 123 | sample = ((sample * volume) >> 16) + 128; 124 | buffer[writePosition] = sample; 125 | writePosition++; 126 | if(writePosition >= bufferSize) 127 | writePosition = 0; 128 | } 129 | } 130 | 131 | inline unsigned char nextSample() 132 | { 133 | unsigned char s = buffer[readPosition]; 134 | readPosition++; 135 | if(readPosition >= bufferSize) 136 | readPosition = 0; 137 | return s; 138 | } 139 | 140 | ///stops playing a sound with an individual id 141 | void stop(int id) 142 | { 143 | SoundBase **soundp = &sounds; 144 | while(*soundp) 145 | { 146 | if((*soundp)->id == id) 147 | { 148 | (*soundp)->remove(soundp); 149 | return; 150 | } 151 | soundp = &(*soundp)->next; 152 | } 153 | } 154 | 155 | void stop(SoundBase *sound) 156 | { 157 | stop(sound->id); 158 | } 159 | }; 160 | 161 | class Wavetable 162 | { 163 | public: 164 | const signed char *samples; 165 | int soundCount; 166 | const int *offsets; 167 | int samplingRate; 168 | 169 | Wavetable(const signed char *samples, int soundCount, const int *offsets, int samplingRate) 170 | { 171 | this->samples = samples; 172 | this->soundCount = soundCount; 173 | this->offsets = offsets; 174 | this->samplingRate = samplingRate; 175 | } 176 | 177 | int play(AudioSystem &audioSystem, int soundNumber, float amplitude = 1, float speed = 1, bool loop = false) 178 | { 179 | Sound *sound = new Sound(); 180 | sound->init(&samples[offsets[soundNumber]], offsets[soundNumber + 1] - offsets[soundNumber], amplitude, speed * samplingRate / audioSystem.samplingRate, loop); 181 | return audioSystem.play(sound); 182 | } 183 | 184 | void stop(AudioSystem &audioSystem, int id) 185 | { 186 | audioSystem.stop(id); 187 | } 188 | }; 189 | -------------------------------------------------------------------------------- /POVLightsaber/POVLightsaber.ino: -------------------------------------------------------------------------------- 1 | #include "Gyro.h" 2 | #include "WS2812BI2S.h" 3 | #include "AudioSystem.h" 4 | #include "AudioOutput.h" 5 | #include "sfx/sounds.h" 6 | #include "SynthSound.h" 7 | 8 | static const int buttonPin = 17; 9 | static const int ledCount = 144; 10 | static const int brightness = 128; 11 | static const int speed = 3; 12 | 13 | unsigned char image[128*128][3]; 14 | int imageRes[] = {128, 128}; 15 | int currentImage = 0; 16 | 17 | #include "File.h" 18 | 19 | bool loadCurrentImage() 20 | { 21 | char filename[32]; 22 | sprintf(filename, "/spiffs/image%d.bin", currentImage); 23 | Serial.println(filename); 24 | if(!readFromFile(filename, image[0], 128 * 128 * 3)) 25 | { 26 | for(int y = 0; y < 128; y++) 27 | for(int x = 0; x < 128; x++) 28 | { 29 | image[y * 128 + x][0] = 0;//x * 2; 30 | image[y * 128 + x][1] = 0;//y * 2; 31 | image[y * 128 + x][2] = 0;//254 - x * 2; 32 | } 33 | return false; 34 | } 35 | return true; 36 | } 37 | 38 | bool saveCurrentImage() 39 | { 40 | char filename[32]; 41 | sprintf(filename, "/spiffs/image%d.bin", currentImage); 42 | Serial.println(filename); 43 | return writeToFile(filename, image[0], 128 * 128 * 3); 44 | } 45 | 46 | Gyro gyro(0, 1); 47 | AudioSystem audioSystem(22050, 500); 48 | AudioOutput audioOutput; 49 | SynthSound *humSound = 0; 50 | 51 | void setup() 52 | { 53 | Serial.begin(115200); 54 | initPixels(); 55 | //initialize audio output 56 | //audioOutput.init(audioSystem); 57 | pinMode(buttonPin, INPUT_PULLUP); 58 | gyro.calculateCorrection(); 59 | initFileSystem(); 60 | loadCurrentImage(); 61 | } 62 | 63 | int pressed = 0; 64 | bool on = false; 65 | int visibleLeds = 0; 66 | bool outputOn = false; 67 | 68 | void turnOn() 69 | { 70 | sounds.play(audioSystem, 0, 0.5, 1); 71 | humSound = new SynthSound(); 72 | humSound->init(audioSystem); 73 | audioSystem.play(humSound); 74 | on = true; 75 | visibleLeds = 0; 76 | gyro.wakeUp(); 77 | } 78 | 79 | void turnOff() 80 | { 81 | sounds.play(audioSystem, 1, 0.5, 1); 82 | audioSystem.stop(humSound); 83 | humSound = 0; 84 | on = false; 85 | visibleLeds = ledCount * speed + 100; 86 | } 87 | 88 | void loopSaber(int dt) 89 | { 90 | static float angle = 0; 91 | gyro.poll(); 92 | float td = sqrt(gyro.rotationV[0] * gyro.rotationV[0] + gyro.rotationV[1] * gyro.rotationV[1] + gyro.rotationV[2] * gyro.rotationV[2]); 93 | float d = gyro.rotationV[2] * dt * 0.001; 94 | angle += d; 95 | Serial.print(gyro.positionA[0]); 96 | Serial.print(" "); 97 | Serial.print(td); 98 | Serial.print(" "); 99 | Serial.println(angle); 100 | 101 | if(td < 5) 102 | { 103 | //standing still (correct angle) 104 | float l = sqrt(gyro.positionA[0] * gyro.positionA[0] + gyro.positionA[1] * gyro.positionA[1] + gyro.positionA[2] * gyro.positionA[2]); 105 | float rl = 1 / ((l == 0)? 1 : l); 106 | angle = angle * 0.9 + acos(rl * gyro.positionA[0]) * 180 / M_PI * 0.1; 107 | } 108 | 109 | int yi = (int)angle % imageRes[1]; 110 | int p = imageRes[0] * yi; 111 | if(humSound) 112 | { 113 | float p = abs(gyro.rotationV[2] * 0.0001) + 1; 114 | if(p > 100) 115 | p = 100; 116 | p *= 256; 117 | humSound->pitch = (int)p; 118 | float v = gyro.rotationV[2] * gyro.rotationV[2] * 0.00001 + 0.5; 119 | if(v > 3) 120 | v = 3; 121 | v *= 256; 122 | float extrusion = (visibleLeds < 0) ? 0 : visibleLeds * 0.002f; 123 | if(extrusion > 1) 124 | extrusion = 1; 125 | v *= extrusion; 126 | humSound->volume = (int)v; 127 | } 128 | //x = towards button 129 | //y = towards volume 130 | //z = wield 131 | //Serial.println(gyro.positionA[1]); 132 | 133 | float sx = -cos(angle * M_PI / 180); 134 | float sy = -sin(angle * M_PI / 180); 135 | 136 | /* image[0][0] = 128; 137 | image[0][1] = 0; 138 | image[0][2] = 0;*/ 139 | int sample = 0; 140 | for(int i = 0; i < pixelCount; i++) 141 | { 142 | int x = 64 + (int)(sx * (i + 20)); 143 | int y = 150 + (int)(sy * (i + 20)); 144 | if(i * speed < visibleLeds) 145 | { 146 | int a = 0; 147 | if(x >= 0 && y >= 0 && x < imageRes[0] && y < imageRes[1]) 148 | a = imageRes[0] * y + x; 149 | pixels[sample++] = bitLUT[((int)image[a][1] * image[a][1]) >> 8]; 150 | pixels[sample++] = bitLUT[((int)image[a][0] * image[a][0]) >> 8]; 151 | pixels[sample++] = bitLUT[((int)image[a][2] * image[a][2]) >> 8]; 152 | /*pixels[sample++] = bitLUT[((int)image[a][1] * brightness) >> 8]; 153 | pixels[sample++] = bitLUT[((int)image[a][0] * brightness) >> 8]; 154 | pixels[sample++] = bitLUT[((int)image[a][2] * brightness) >> 8];*/ 155 | /*pixels[sample++] = bitLUT[image[a][1]]; 156 | pixels[sample++] = bitLUT[image[a][0]]; 157 | pixels[sample++] = bitLUT[image[a][2]];*/ 158 | } 159 | else 160 | { 161 | pixels[sample++] = bitLUT[0]; 162 | pixels[sample++] = bitLUT[0]; 163 | pixels[sample++] = bitLUT[0]; 164 | } 165 | } 166 | i2s_write_bytes(i2s_num, (char*)pixels, allocatedSamples * 4, portMAX_DELAY); 167 | } 168 | 169 | void loop() 170 | { 171 | static int time = 0; 172 | int t = millis(); 173 | int dt = t - time; 174 | time = t; 175 | 176 | if(!digitalRead(buttonPin)) 177 | { 178 | pressed += dt; 179 | if(pressed > 1000) 180 | { 181 | if(on) 182 | turnOff(); 183 | else 184 | { 185 | if(!outputOn) 186 | { 187 | audioOutput.init(audioSystem); 188 | outputOn = true; 189 | } 190 | turnOn(); 191 | } 192 | pressed = -10000; 193 | } 194 | } 195 | else 196 | { 197 | if(pressed > 100) 198 | { 199 | currentImage = (currentImage + 1) & 3; 200 | loadCurrentImage(); 201 | } 202 | pressed = 0; 203 | } 204 | 205 | loopSaber(dt); 206 | 207 | if(on) 208 | visibleLeds += dt; 209 | else 210 | visibleLeds -= dt; 211 | 212 | //fill audio buffer 213 | audioSystem.calcSamples(); 214 | } 215 | 216 | --------------------------------------------------------------------------------