2016年10月10日月曜日

ベースマシン BaseMachine Sequncerのプログラミング

BaseMachine Sequencer (SPI Slave)
https://developer.mbed.org/users/ryood/code/BaseMachine_Sequencer/ Revision:40

main.cpp

/*
 * main.cpp
 * SpiSequencerSender_test
 *
 * 2016.10.10 UIを分離
 * 2016.08.20 mbed Rev 121 / mbed-rtos Rev 117で動作確認
 *
 */

#include "mbed.h"
#include "rtos.h"


#define UART_TRACE  (0)
#include "SpiSequenceSender.h"
#include "EnvelopeGenerator.h"
#include "SpiAmpController.h"
#include "SpiFilterController.h"

#define TITLE_STR1  ("BaseMachine Sequencer")
#define TITLE_STR2  ("20161010")

#define SEQUENCE_N  (16)
#define SPI1_RATE   (10000000)
#define SPI2_RATE   (3000000)   // Actual frequency about 2.6MHz

enum SequencerCmd {
    CMD_RCV_PLAYING_STEP = 0x01,
    CMD_RUN              = 0x11,
    CMD_BPM              = 0x12,
    CMD_ACCENT_LEVEL     = 0x13,
    CMD_WAVE_SHAPE       = 0x21,
    CMD_PULSE_WIDTH      = 0x22,
    CMD_CUTOFF           = 0x31,
    CMD_RESONANCE        = 0x32,
    CMD_LEVEL            = 0x41,
    CMD_DURATION         = 0x42,
    CMD_DECAY            = 0x43,
    CMD_SUSTAIN          = 0x44,
    CMD_NOTE             = 0x51,
    CMD_PITCH            = 0x52, 
};

const int samplingPeriod = 1;   // ms
const int bpm = 120;
const int envelopeLength = (60 * 1000 / (bpm * 4)) / samplingPeriod;
const int waveShape = SpiSequenceSender::WAVESHAPE_SQUARE;
const int baseNoteNumber = 36;

// Initial Sequence
const int noteOn[SEQUENCE_N] = { 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0 };
const int pitch[SEQUENCE_N]  = {36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36 };
const int tie[SEQUENCE_N]    = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
const int accent[SEQUENCE_N] = { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 };

// Devices
//
//SPI1 (PinName mosi, PinName miso, PinName sclk, PinName ssel=NC)
SPI SpiMaster(PA_7, PA_6, PA_5);

//SPI2 (PinName mosi, PinName miso, PinName sclk, PinName ssel)
SPISlave SpiSlave(PB_15, PB_14, PB_13, PB_12);
DigitalOut StepChangePin(PB_1);

BusOut Leds(PA_0, PA_1, PA_4, PB_0, PC_1, PC_0, PA_10, PB_3);

// Grobal Variables
//
Sequence sequences[SEQUENCE_N];
SpiSequenceSender sequenceSender(&SpiMaster, D9, sequences, SEQUENCE_N, samplingPeriod, bpm);

Envelope envelope(4095, envelopeLength, envelopeLength*3/4, envelopeLength/2, 2047);
EnvelopeGenerator envelopeGenerator;
SpiAmpController ampController(&SpiMaster, D8, D7);

SpiFilterController filterController(&SpiMaster, D10);

int playingStep = 0;
int editingStep = 0;
bool isRunning = false;

//------------------------------------------------------------------------
// Callback functions
//------------------------------------------------------------------------
void updateTicks(int ticks)
{
    if (ticks == 0) {
        envelopeGenerator.init(envelope);
        playingStep = sequenceSender.getStep();
        // Masterにinterruptをかける。
        StepChangePin.write(1);
        StepChangePin.write(0);
    }

    if (sequences[sequenceSender.getStep()].isNoteOn()) {
        uint16_t level = envelopeGenerator.getModLevel();
        if (!sequences[sequenceSender.getStep()].isAccent()) {
            level = level * 1 / 2;
        }
        ampController.outDca(level);
    } else {
        ampController.outDca(0);
    }
    envelopeGenerator.update();
    
    filterController.outDcf();
}

//------------------------------------------------------------------------
// Functions
//------------------------------------------------------------------------

void spiSlaveRecieve()
{
    if (SpiSlave.receive()) {
        SpiSlave.reply(playingStep);
        uint16_t recievedVal = SpiSlave.read();
        uint8_t cmd  = recievedVal >> 8;
        uint8_t data = recievedVal & 0xff;
        
        if (cmd != CMD_RCV_PLAYING_STEP) {
            Leds.write(cmd);
        }
        
        switch(cmd) {
        case CMD_RCV_PLAYING_STEP:
            // Do nothing
            break;
        case CMD_RUN:
            if (data == 0) {
                ampController.outDca(0);
                sequenceSender.stop();
                isRunning = false;
            }
            else {
                sequenceSender.run(playingStep);
                isRunning = true;
            }
            break;
        case CMD_BPM:
            sequenceSender.setBpm(data);
            break;
        case CMD_ACCENT_LEVEL:
            // ToDo: Impl. accentLevel
            break;
        case CMD_WAVE_SHAPE:
            sequenceSender.setWaveShape(data);   
            break;
        case CMD_PULSE_WIDTH:
            sequenceSender.setPulseWidth(data << 1);
            break;
        case CMD_CUTOFF:
            filterController.setCutoff(data << 1);
            break;
        case CMD_RESONANCE:
            filterController.setResonance(data << 1);
            break;
        case CMD_LEVEL:
            envelope.setLevel(data << 5);
            break;
        case CMD_DURATION:
            envelope.setDuration((float)data / 128.0f * envelopeLength);
            break;
        case CMD_DECAY:
            envelope.setDecay((float)data / 128.0f * envelopeLength);
            break;
        case CMD_SUSTAIN:
            envelope.setSustain(data << 5);
            break;
        case CMD_NOTE:
            editingStep = data >> 4;
            sequences[editingStep].setNoteOn(data & 0x01);
            sequences[editingStep].setTie((data & 0x02) >> 1);
            sequences[editingStep].setAccent((data & 0x04) >> 2);
            break;
        case CMD_PITCH:
            sequences[editingStep].setPitch(data);
            break;
        default:
            Leds.write(cmd);
            error("invalid SPI cmd");
        }
    }
}

//------------------------------------------------------------------------
// Main routine
//------------------------------------------------------------------------
int main()
{
    #if (UART_TRACE)
    printf("*** BaseMachine Sequencer ***\r\n");
    #endif
    
    // Check LED
    for (int i = 0; i < 8; i++) {
        Leds[i].write(1);
        Thread::wait(100);
    }
    Thread::wait(500);
    Leds.write(0x00);
    
    //--------------------------------------------------------------------
    // Setup Devices
    //
    SpiMaster.format(8, 0);
    SpiMaster.frequency(SPI1_RATE);
    
    SpiSlave.format(16, 0);
    SpiSlave.frequency(SPI2_RATE);
    
    // Mute output
    ampController.outDca(0);
    
    //--------------------------------------------------------------------
    // Initialize objects
    //
    Sequence::setBaseNoteNumber(baseNoteNumber);
    
    for (int i = 0; i < SEQUENCE_N; i++) {
        Sequence& seq = sequenceSender.getSequences()[i];
        seq.setPitch(pitch[i]);
        seq.setNoteOn(noteOn[i]);
        seq.setTie(tie[i]);
        seq.setAccent(accent[i]);
    }
    
    envelopeGenerator.init(envelope);

    sequenceSender.attachUpdate(&updateTicks);
    sequenceSender.setWaveShape(waveShape);
    
    //--------------------------------------------------------------------
    // Main loop
    //
    for (;;) {
        spiSlaveRecieve();
    }
 
}

UIとSequencer本体をMPUを分離したおかげでファームウェアは随分スッキリした。(実際の動作はクラスにカプセル化しているが)

BaseMachine UI Controller (SPI Master)

https://developer.mbed.org/users/ryood/code/BaseMachine_UI_Controller/ Revision:48

メモ:


MasterとSlaveで電源の入れる順番とパラメーターの変更の仕方でうまく動作しない場合がある。というかほとんどの場合初期状態で動作しない(^q^;

設定していないコマンドを受け取ったときにerror()を呼び出して停止するようにしているので、これでひっかかりがちなのかも。

ちなみにerror()という関数はmbedに用意されているもので、引数の文字列をUARTで出力して、ボード上の緑色のLEDが点滅して停止する。

UI Controllerの起動時に一旦全パラメーターをSequencerに送って、データを同期するようにした方がいいか。

MasterとSlaveで共有する設定値は定義ファイルとして分離するべきか。