Platform library for RETRO
Diff: Sound.cpp
- Revision:
- 0:6f26c31d8573
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Sound.cpp Sun Mar 01 05:29:45 2015 +0000 @@ -0,0 +1,515 @@ +/* + * (C) Copyright 2015 Valentin Ivanov. All rights reserved. + * + * This file is part of the RetroPlatform Library + * + * The RetroPlatform Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + * This library is inspired by Gamebuino Library (http://gamebuino.com) + * from Aurélien Rodot. + */ +#include "Sound.h" +#include "Utils.h" + +#if(NUM_CHANNELS > 0) +uint8_t _rand = 1; +const uint16_t squareWaveInstrument[] = {0x0101, 0x03F7}; +const uint16_t noiseInstrument[] = {0x0101, 0x03FF}; +const uint16_t* const defaultInstruments[] = {squareWaveInstrument,noiseInstrument}; + +#define NUM_PITCH 59 + +const uint8_t _halfPeriods[NUM_PITCH] = { + /*268,*/253,239,225,213,201,190,179,169,159,150,142, + 134,127,119,113,106,100,95,89,84,80,75,71,67,63,60, + 56,53,50,47,45,42,40,38,36,34,32,30,28,27,25,24,22, + 21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6 +}; + +#endif + +Sound::Sound() : speaker(P0_18) +{ + initialize(); +} + +void Sound::initialize() +{ +#if(NUM_CHANNELS > 0) + volumeMax = VOLUME_GLOBAL_MAX; + globalVolume = VOLUME_GLOBAL_MAX; + prescaler = 1; + + speaker.period_us(32); + speaker.write(0.0); + + for(uint8_t channel=0; channel<NUM_CHANNELS; channel++) { + chanVolumes[channel] = VOLUME_CHANNEL_MAX; + changeInstrumentSet(defaultInstruments, channel); + command(CMD_INSTRUMENT, 0, 0, channel); + } + timer.attach_us(this, &Sound::generateOutput, 16); //62500Hz +#endif +} + +void Sound::playTrack(const uint16_t* track, uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + stopTrack(channel); + tracks[channel].Cursor = 0; + tracks[channel].Data = (uint16_t*)track; + tracks[channel].IsPlaying = true; +#endif +} + +void Sound::stopTrack(uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + tracks[channel].IsPlaying = false; + stopSequence(channel); +#endif +} + +void Sound::updateTrack(uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + if(tracks[channel].IsPlaying && !sequences[channel].IsPlaying) { + uint16_t data = tracks[channel].Data[tracks[channel].Cursor]; + if(data == 0xFFFF) { + tracks[channel].IsPlaying = false; + return; + } + uint8_t patternID = data & 0xFF; + data >>= 8; + patternPitch[channel] = data; + playSequence((const uint16_t*)patternSet[channel][patternID], channel); + tracks[channel].Cursor++; + } +#endif +} + + +void Sound::changeSequenceSet(const uint16_t* const* patterns, uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + patternSet[channel] = (uint16_t**)patterns; +#endif +} + +void Sound::playSequence(const uint16_t* pattern, uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + stopSequence(channel); + sequences[channel].Data = (uint16_t*)pattern; + sequences[channel].Cursor = 0; + sequences[channel].IsPlaying = true; + noteVolume[channel] = 9; + //reinit commands + commands[channel].volumeSlideStepDuration = 0; + commands[channel].arpeggioStepDuration = 0; + commands[channel].tremoloStepDuration = 0; +#endif +} + +void Sound::changeInstrumentSet(const uint16_t* const* instruments, uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + instrumentSet[channel] = (uint16_t**)instruments; +#endif +} + +void Sound::updateSequence(uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + if(sequences[channel].IsPlaying) { + if(noteDuration[channel]==0) { //if the end of the previous note is reached + + uint16_t data = sequences[channel].Data[sequences[channel].Cursor]; + + if(data == 0) { //end of the pattern reached + if(sequences[channel].Looping == true) { + sequences[channel].Cursor = 0; + data = sequences[channel].Data[sequences[channel].Cursor]; + } else { + sequences[channel].IsPlaying = false; + if(tracks[channel].IsPlaying) { //if this pattern is part of a track, get the next pattern + updateTrack(channel); + data = sequences[channel].Data[sequences[channel].Cursor]; + } else { + stopNote(channel); + return; + } + } + } + + while (data & 0x0001) { //read all commands and instrument changes + data >>= 2; + uint8_t cmd = data & 0x0F; + data >>= 4; + uint8_t X = data & 0x1F; + data >>= 5; + int8_t Y = data - 16; + command(cmd,X,Y,channel); + sequences[channel].Cursor++; + data = sequences[channel].Data[sequences[channel].Cursor]; + } + data >>= 2; + + uint8_t pitch = data & 0x003F; + data >>= 6; + + uint8_t duration = data; + + playNote(pitch, duration, channel); + + sequences[channel].Cursor++; + } + } +#endif +} + +void Sound::stopSequence(uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + stopNote(channel); + sequences[channel].IsPlaying = false; +#endif +} + +void Sound::command(uint8_t cmd, uint8_t X, int8_t Y, uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + + switch(cmd) { + case CMD_VOLUME: //volume + X = constrain(X, 0, 10); + noteVolume[channel] = X; + break; + case CMD_INSTRUMENT: //instrument + instruments[channel].Data = (uint16_t*)instrumentSet[channel][X]; + instruments[channel].Length = instruments[channel].Data[0] & 0x00FF; //8 LSB + instruments[channel].Length *= prescaler; + instruments[channel].Looping = min(instruments[channel].Data[0] >> 8, instruments[channel].Length); //8 MSB - check that the loop is shorter than the instrument length + instruments[channel].Looping *= prescaler; + break; + case CMD_SLIDE: //volume slide + commands[channel].volumeSlideStepDuration = X * prescaler; + commands[channel].volumeSlideStepSize = Y; + break; + case CMD_ARPEGGIO: + commands[channel].arpeggioStepDuration = X * prescaler; + commands[channel].arpeggioStepSize = Y; + break; + case CMD_TREMOLO: + commands[channel].tremoloStepDuration = X * prescaler; + commands[channel].tremoloStepSize = Y; + break; + default: + break; + } +#endif +} + +void Sound::playNote(uint8_t pitch, uint8_t duration, uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + + //set note + notePitch[channel] = pitch; + noteDuration[channel] = duration * prescaler; + notePlaying[channel] = true; + + //reinit vars + instruments[channel].NextChange = 0; + instruments[channel].Cursor = 0; + + commands[channel].Counter = 0; + + _chanState[channel] = true; +#endif +} + +void Sound::stopNote(uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + notePlaying[channel] = false; + //counters + noteDuration[channel] = 0; + instruments[channel].Cursor = 0; + commands[channel].Counter = 0; + //output + _chanOutput[channel] = 0; + _chanOutputVolume[channel] = 0; + _chanState[channel] = false; + updateOutput(); +#endif +} + +void Sound::updateNote(uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + if (notePlaying[channel]) { + + if(noteDuration[channel] == 0) { + stopNote(channel); + return; + } else { + noteDuration[channel]--; + } + + if (instruments[channel].NextChange == 0) { + + //read the step data from the progmem and decode it + uint16_t thisStep = instruments[channel].Data[1 + instruments[channel].Cursor]; + + stepVolume[channel] = thisStep & 0x0007; + thisStep >>= 3; + + uint8_t stepNoise = thisStep & 0x0001; + thisStep >>= 1; + + uint8_t stepDuration = thisStep & 0x003F; + thisStep >>= 6; + + stepPitch[channel] = thisStep; + + //apply the step settings + instruments[channel].NextChange = stepDuration * prescaler; + + _chanNoise[channel] = stepNoise; + + instruments[channel].Cursor++; + + if (instruments[channel].Cursor >= instruments[channel].Length) { + if (instruments[channel].Looping) { + instruments[channel].Cursor = instruments[channel].Length - instruments[channel].Looping; + } else { + stopNote(channel); + } + } + } + instruments[channel].NextChange--; + + commands[channel].Counter++; + + outputPitch[channel] = notePitch[channel] + stepPitch[channel] + patternPitch[channel]; + + if(commands[channel].arpeggioStepDuration) + outputPitch[channel] += commands[channel].Counter / commands[channel].arpeggioStepDuration * commands[channel].arpeggioStepSize; + + outputPitch[channel] = outputPitch[channel] % NUM_PITCH; //wrap + + //volume + outputVolume[channel] = noteVolume[channel]; + if(commands[channel].volumeSlideStepDuration) + outputVolume[channel] += commands[channel].Counter / commands[channel].volumeSlideStepDuration * commands[channel].volumeSlideStepSize; + + if(commands[channel].tremoloStepDuration) + outputVolume[channel] += ((commands[channel].Counter / commands[channel].tremoloStepDuration) % 2) * commands[channel].tremoloStepSize; + + outputVolume[channel] = constrain(outputVolume[channel], 0, 9); + + if(notePitch[channel] == 63) + outputVolume[channel] = 0; + + __disable_irq(); + _chanHalfPeriod[channel] = _halfPeriods[outputPitch[channel]]; + _chanOutput[channel] = _chanOutputVolume[channel] = outputVolume[channel] * globalVolume * chanVolumes[channel] * stepVolume[channel]; + __enable_irq(); + } +#endif +} + +void Sound::setChannelHalfPeriod(uint8_t channel, uint8_t halfPeriod) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + _chanHalfPeriod[channel] = halfPeriod; + _chanState[channel] = false; + _chanCount[channel] = 0; + updateOutput(); +#endif +} + + +void Sound::generateOutput() +{ +#if(NUM_CHANNELS > 0) + boolean outputChanged = false; + //no for loop here, for the performance sake (this function runs 15 000 times per second...) + //CHANNEL 0 + if (_chanOutputVolume[0]) { + _chanCount[0]++; + if (_chanCount[0] >= _chanHalfPeriod[0]) { + outputChanged = true; + _chanState[0] = !_chanState[0]; + _chanCount[0] = 0; + if (_chanNoise[0]) { + _rand = 67 * _rand + 71; + _chanOutput[0] = _rand % _chanOutputVolume[0]; + } + } + } + + //CHANNEL 1 +#if (NUM_CHANNELS > 1) + if (_chanOutputVolume[1]) { + _chanCount[1]++; + if (_chanCount[1] >= _chanHalfPeriod[1]) { + outputChanged = true; + _chanState[1] = !_chanState[1]; + _chanCount[1] = 0; + if (_chanNoise[1]) { + _rand = 67 * _rand + 71; + _chanOutput[1] = _rand % _chanOutputVolume[1]; + } + } + } +#endif + + //CHANNEL 2 +#if (NUM_CHANNELS > 2) + if (_chanOutputVolume[2]) { + _chanCount[2]++; + if (_chanCount[2] >= _chanHalfPeriod[2]) { + outputChanged = true; + _chanState[2] = !_chanState[2]; + _chanCount[2] = 0; + if (_chanNoise[2]) { + _rand = 67 * _rand + 71; + _chanOutput[2] = _rand % _chanOutputVolume[2]; + } + } + } +#endif + + //CHANNEL 3 +#if (NUM_CHANNELS > 3) + if (_chanOutputVolume[3]) { + _chanCount[3]++; + if (_chanCount[3] >= _chanHalfPeriod[3]) { + outputChanged = true; + _chanState[3] = !_chanState[3]; + _chanCount[3] = 0; + if (_chanNoise[3]) { + _rand = 67 * _rand + 71; + _chanOutput[3] = _rand % _chanOutputVolume[3]; + } + } + } +#endif + + if (outputChanged) { + updateOutput(); + } +#endif +} + +void Sound::updateOutput() +{ +#if(NUM_CHANNELS > 0) + uint8_t output = 0; + + //CHANNEL 0 + if (_chanState[0]) { + output += _chanOutput[0]; + } + + //CHANNEL 1 +#if (NUM_CHANNELS > 1) + if (_chanState[1]) { + output += _chanOutput[1]; + } +#endif + + //CHANNEL 2 +#if (NUM_CHANNELS > 2) + if (_chanState[2]) { + output += _chanOutput[2]; + } +#endif + + //CHANNEL 3 +#if (NUM_CHANNELS > 3) + if (_chanState[3]) { + output += _chanOutput[3]; + } +#endif + speaker.write(output/255.0); +#endif +} + + +void Sound::setVolume(int8_t volume) +{ +#if NUM_CHANNELS > 0 + globalVolume = volume % (volumeMax+1); +#endif +} + +uint8_t Sound::getVolume() +{ +#if NUM_CHANNELS > 0 + return globalVolume; +#else + return 0; +#endif +} + +void Sound::setVolume(int8_t volume, uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return; + volume = (volume > VOLUME_CHANNEL_MAX) ? VOLUME_CHANNEL_MAX : volume; + volume = (volume < 0) ? 0 : volume; + chanVolumes[channel] = volume; +#endif +} + +uint8_t Sound::getVolume(uint8_t channel) +{ +#if(NUM_CHANNELS > 0) + if(channel>=NUM_CHANNELS) + return 255; + return (chanVolumes[channel]); +#else + return 0; +#endif +} \ No newline at end of file