Open erstlaub opened 7 years ago
That's a good idea. :)
We could add options to control what the start Pot and CV do so they can be pitch or start independently of each other.
I'm not sure if the reset input is just a digital 0/1 or if its a full CV like Station and Start. IF it is full CV then i'd certainly add configuration for that to a future upgrade.
In the long term i'd like to see an expander so you can control all the things at the same time but I have no idea if this would require completely new hardware on the unit itself.
Giving this a little more though overnight, I feel like the reset input should probably remain unmeddled with and the start knob/input split if possible. (re)Triggering from a sequencer is too useful a function to option out I reckon.
I was trying to think about the most useful configuration for me and I got to thinking that most users are likely to have either access to offsets and/or some sort of CV based control device -sequencer/KB/etc so it'd just be down each user deciding which worked better for them and setting I suppose a line in cfg to activate the split behaviour and then a second line to state whether you'd want pitch or start on the knob.
The one possible issue that would need some consideration in the absence of concord among manufacturers would be the ranges involved, particularly with pitch. Am I correct in thinking that the RM hardware can not add (well subtract really) a -ve voltage value from a parameter? Given that offsets (say -10 to +10v) vary from a CV keyboard (some claim 0v lowest value, some will go down to -3v or more) the playback might need to default to it's lowest possible value with no input and then have the CV values added (in order to be able to access the slower as well as faster pitched results). This is based on an ultra vague knowledge that arduino based processors (maybe all processors) tend not to cope will with things outside the 0-5v ranges (or have I made this up?).
Just a few little considerations that might help the thought process, save some cul de sacs or gain some useful insight from other users.
Best.
D
I also need to select the start currently I use the reset button to switch between picth/start (works with the immediate mode only)
if ((changes & BUTTON_SHORT_PRESS )&& !speedChangeMode){
D(Serial.println("enter speedmode"););
speedChangeMode =true;
} else if ((changes & BUTTON_SHORT_PRESS )&& speedChangeMode){
D(Serial.println("exit speedmode"););
speedChangeMode =false;
}
and :
if((changes & CHANGE_START_NOW ) && speedChangeMode ) {
speedChange = true;
} else if((changes & CHANGE_START_NOW) && !speedChangeMode) {
skipToStartPoint = true;
}
Ive spent the last hour or so being too dumb to work out how to even be able to find the source code that I'd need to add this to in order to then make back into a hex file.
So far have tried opening the binary in some hex editors (nope), txt edit (nope) and the teensy app (just for actually uploading finished binaries).
Any tips?
//despair
To compile your own firmware and upload do this :
As for adding the code changes from @davidhbot its best that he explains exactly where he made them. :)
Thanks for the info, I was actually just missing the teensyduino step.
Sadly once I got in looking at the arduino sketches I realised this is well beyond my previous 'make a stepper motor move at a certain speed' experience.
Would definitely love to hear how/where @davidhbot has implemented the code so I can attempt it myself.
Hi @erstlaub
here is my code, warning ! it's quick and dirty and should work only in immediate mode checks things around speedChangeMode
/*
RADIO MUSIC
https://github.com/TomWhitwell/RadioMusic
Audio out: Onboard DAC, teensy3.1 pin A14/DAC
Bank Button: 2
Bank LEDs 3,4,5,6
Reset Button: 8
Reset LED 11
Reset CV input: 9
Channel Pot: A9
Channel CV: A8 // check
Time Pot: A7
Time CV: A6 // check
SD Card Connections:
SCLK 14
MISO 12
MOSI 7
SS 10
NB: Compile using modified versions of:
SD.cpp (found in the main Arduino package)
play_sd_raw.cpp - In Teensy Audio Library
play_sc_raw.h - In Teensy Audio Library
from:https://github.com/TomWhitwell/RadioMusic/tree/master/Collateral/Edited%20teensy%20files
Additions and changes:
2016 by Jouni Stenroos - jouni.stenroos@iki.fi
- New bank change mode
- Removing 330 file limit
- Improving reset
- File sorting
- Audio crossfade
- Some refactoring and organization of code.
*/
#include <EEPROM.h>
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include "RadioMusic.h"
#include "AudioSystemHelpers.h"
#include "Settings.h"
#include "LedControl.h"
#include "FileScanner.h"
#include "AudioEngine.h"
#include "Interface.h"
#include "PlayState.h"
#ifdef DEBUG
#define D(x) x
#else
#define D(x)
#endif
// Press reset button to reboot
//#define RESET_TO_REBOOT
//#define ENGINE_TEST
#define EEPROM_BANK_SAVE_ADDRESS 0
#define FLASHTIME 10 // How long do LEDs flash for?
#define SHOWFREQ 250 // how many millis between serial Debug updates
#define peakFPS 30 // FRAMERATE FOR PEAK METER
#define SD_CARD_CHECK_DELAY 20
// //////////
// TIMERS
// //////////
elapsedMillis showDisplay;
elapsedMillis resetLedTimer = 0;
elapsedMillis ledFlashTimer = 0;
elapsedMillis meterDisplayTimer; // Counter to hide MeterDisplay after bank change
elapsedMillis fps; // COUNTER FOR PEAK METER FRAMERATE
int prevBankTimer = 0;
boolean flashLeds = false;
boolean bankChangeMode = false;
boolean speedChangeMode = false;
File settingsFile;
Settings settings("SETTINGS.TXT");
LedControl ledControl;
FileScanner fileScanner;
AudioEngine audioEngine;
Interface interface;
PlayState playState;
int NO_FILES = 0;
void setup() {
#ifdef DEBUG_STARTUP
while( !Serial );
Serial.println("Starting");
#endif // DEBUG_STARTUP
ledControl.init();
ledControl.single(playState.bank);
// MEMORY REQUIRED FOR AUDIOCONNECTIONS
AudioMemory(20);
// SD CARD SETTINGS FOR AUDIO SHIELD
SPI.setMOSI(7);
SPI.setSCK(14);
boolean hasSD = openSDCard();
if(!hasSD) {
Serial.println("Rebooting");
reBoot(0);
}
settings.init(hasSD);
File root = SD.open("/");
fileScanner.scan(&root, settings);
getSavedBankPosition();
audioEngine.init(settings);
int numFiles = fileScanner.numFilesInBank[playState.bank];
D(Serial.print("File Count ");Serial.println(numFiles););
if(numFiles == 0) {
NO_FILES = 1;
}
interface.init(fileScanner.fileInfos[playState.bank][0].size, fileScanner.numFilesInBank[playState.bank], settings, &playState);
D(Serial.println("--READY--"););
}
void getSavedBankPosition() {
// CHECK FOR SAVED BANK POSITION
int a = 0;
a = EEPROM.read(EEPROM_BANK_SAVE_ADDRESS);
if (a >= 0 && a <= fileScanner.activeBanks) {
playState.bank = a;
playState.channelChanged = true;
} else {
EEPROM.write(EEPROM_BANK_SAVE_ADDRESS, 0);
};
}
boolean openSDCard() {
int crashCountdown = 0;
if (!(SD.begin(SS))) {
Serial.println("No SD.");
while (!(SD.begin(SS))) {
ledControl.single(15);
delay(SD_CARD_CHECK_DELAY);
ledControl.single(crashCountdown % 4);
delay(SD_CARD_CHECK_DELAY);
crashCountdown++;
Serial.print("Crash Countdown ");
Serial.println(crashCountdown);
if (crashCountdown > 4) {
return false;
}
}
}
return true;
}
void loop() {
#ifdef CHECK_CPU
checkCPU();
// audioEngine.measure();
#endif
if(NO_FILES) {
// TODO : Flash the lights to show there are no files
return;
}
updateInterfaceAndDisplay();
audioEngine.update();
if(audioEngine.error) {
// Too many read errors, reboot
Serial.println("Audio Engine errors. Reboot");
reBoot(0);
}
if (playState.channelChanged) {
D(
Serial.print("RM: Going to next channel : ");
if(playState.channelChanged) Serial.print("RM: Channel Changed. ");
if(audioEngine.eof) Serial.print("End of file.");
Serial.println("");
);
playState.currentChannel = playState.nextChannel;
audioEngine.changeTo(&fileScanner.fileInfos[playState.bank][playState.nextChannel]);
playState.channelChanged = false;
resetLedTimer = 0;
}
}
void updateInterfaceAndDisplay() {
uint16_t changes = checkInterface();
updateDisplay(changes);
}
void updateDisplay(uint16_t changes) {
if (showDisplay > SHOWFREQ) {
showDisplay = 0;
}
if (bankChangeMode) {
ledControl.showReset(1);// Reset led is on continuously when in bank change mode..
if(!flashLeds) {
ledControl.multi(playState.bank);
}
} else {
ledControl.showReset(resetLedTimer < FLASHTIME); // flash reset LED
}
if (speedChangeMode) {
ledControl.showReset(100);
}else {
ledControl.showReset(resetLedTimer < FLASHTIME); // flash reset LED
}
if (flashLeds) {
if (ledFlashTimer < FLASHTIME * 4) {
ledControl.multi(0x0F);
} else if(ledFlashTimer < FLASHTIME * 8) {
ledControl.multi(0);
} else {
ledFlashTimer = 0;
}
} else if (settings.showMeter && !bankChangeMode) {
peakMeter();
}
}
// INTERFACE //
uint16_t checkInterface() {
uint16_t changes = interface.update();
#ifdef RESET_TO_REBOOT
if (changes & BUTTON_SHORT_PRESS) {
reBoot(0);
}
#endif
// BANK MODE HANDLING
if((changes & BUTTON_LONG_PRESS) && !bankChangeMode) {
D(Serial.println("Enter bank change mode"););
bankChangeMode = true;
flashLeds = true;
ledFlashTimer = 0;
} else if((changes & BUTTON_LONG_RELEASE) && bankChangeMode) {
D(Serial.println("Exit bank change mode"););
flashLeds = false;
bankChangeMode = false;
}
if(changes & BUTTON_PULSE) {
flashLeds = false;
if(bankChangeMode) {
D(Serial.println("BUTTON PULSE"););
nextBank();
} else {
D(Serial.println("Button Pulse but not in bank mode"););
}
}
boolean resetTriggered = changes & RESET_TRIGGERED;
bool skipToStartPoint = false;
bool speedChange = false;
// if ( changes != 0){
// D(Serial.println("changes : " + (String) changes););
// }
if(settings.speedControl) {
if(resetTriggered && !settings.looping) {
skipToStartPoint = true;
}
if((changes & CHANGE_START_NOW ) && speedChangeMode ) {
speedChange = true;
} else if((changes & CHANGE_START_NOW) && !speedChangeMode) {
skipToStartPoint = true;
}
// If start Pot or start CV have changed and they are immediate
// change speed
if ((changes & BUTTON_SHORT_PRESS )&& !speedChangeMode){
D(Serial.println("enter speedmode"););
speedChangeMode =true;
} else if ((changes & BUTTON_SHORT_PRESS )&& speedChangeMode){
D(Serial.println("exit speedmode"););
speedChangeMode =false;
}
else if(resetTriggered && !speedChangeMode ) {
D(Serial.println("RESET"););
skipToStartPoint = true;
}
}
if((changes & CHANNEL_CHANGED) && resetTriggered) {
D(Serial.println("channel changed"););
playState.channelChanged = true;
}
if(speedChange) doSpeedChange();
if(skipToStartPoint && !playState.channelChanged) {
//if(settings.speedControl) {
// audioEngine.skipTo(0);
// } else {
audioEngine.skipTo(interface.time);
// }
}
return changes;
}
void doSpeedChange() {
// SPEED CHANGE
// first map : -1650 to 1650
float speed = 1.0;
if(settings.quantizeNote) {
speed = (float)map(interface.time, 0, 8192, 0, settings.noteRange);
speed -= (int) settings.noteRange / 2;
speed = pow(2,speed / 12);
} else {
//speed = (float)map(interface.time, 0, 8192, -1625,1625);
speed = (float)map(interface.time, 0, 8192, -3000,1000);
speed = pow(2, speed * 0.001);
}
audioEngine.setPlaybackSpeed(speed);
}
void nextBank() {
if(fileScanner.activeBanks == 1) {
D(Serial.println("Only 1 bank."););
return;
}
playState.bank++;
if (playState.bank > fileScanner.activeBanks) {
playState.bank = 0;
}
if (playState.nextChannel >= fileScanner.numFilesInBank[playState.bank])
playState.nextChannel = fileScanner.numFilesInBank[playState.bank] - 1;
interface.setChannelCount(fileScanner.numFilesInBank[playState.bank]);
playState.channelChanged = true;
D(
Serial.print("RM: Next Bank ");
Serial.println(playState.bank);
);
meterDisplayTimer = 0;
EEPROM.write(EEPROM_BANK_SAVE_ADDRESS, playState.bank);
}
#ifdef ENGINE_TEST
boolean tested = false;
int testIndex = 0;
void engineTest() {
if(!tested) {
audioEngine.test(fileScanner.fileInfos[playState.bank][0],fileScanner.fileInfos[playState.bank][1]);
tested = true;
}
uint8_t changes = interface.update();
if(changes & BUTTON_SHORT_PRESS) {
testIndex += 2;
if(testIndex >= fileScanner.numFilesInBank[playState.bank]) {
Serial.println("Back to start");
testIndex = 0;
}
audioEngine.test(fileScanner.fileInfos[playState.bank][testIndex],fileScanner.fileInfos[playState.bank][testIndex+1]);
}
return;
}
#endif
void peakMeter() {
if( (fps < 50) || (meterDisplayTimer < settings.meterHide) ) return;
float peakReading = audioEngine.getPeak();
int monoPeak = round(peakReading * 4);
monoPeak = round(pow(2, monoPeak));
ledControl.multi(monoPeak - 1);
fps = 0;
}
Thanks again for this great work, I'm loving the pitchshifting but really missing the ability to dial in a reset point.
I fully appreciate the limitations of the hardware is but was wondering if it would be at all possible to separate the start knob and the start CV input in order to be able to set a start point via CV or even via the reset input?
At a very vague stab some of the following options might work if possible
Pitch via start knob, start position via start CVin, button resets Pitch via start knob + start CVin, start position set by CV into reset input, button reset to start point
Given the variety of ways that people use RM, perhaps a setting in the config could enable a 'pitch+start with some sacrifices' or 'original flavour' type selection?
The other thing that might an option could be increasing the press delay between bank select and allowing a button hold AND knob movement to change start point where just a knob read would affect pitch as it does?
It's just some spitballing given the brief time I've had tonight enjoying pitching things all the way down but recognising how nice it would be to then be able to restart/trigger certain mid transient whalesongs on demand rather than the reset button going all the way to the start.
Thanks in advance, more than happy for anyone smarter to come up with more logical solutions (or to be shot down entirely).
D