Platform library for RETRO
Sound.cpp
- Committer:
- Architect
- Date:
- 2015-03-01
- Revision:
- 0:6f26c31d8573
File content as of revision 0:6f26c31d8573:
/* * (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 }