You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

412 lines
15KB

  1. #include "BaconPlugs.hpp"
  2. #include <sstream>
  3. #include <vector>
  4. #include <string>
  5. #include "dsp/digital.hpp"
  6. #include "KSSynth.hpp"
  7. namespace rack_plugin_BaconMusic {
  8. struct KarplusStrongPoly : virtual Module {
  9. enum ParamIds {
  10. INITIAL_PACKET,
  11. FILTER_TYPE,
  12. FREQ_KNOB,
  13. ATTEN_KNOB,
  14. FILTER_KNOB_A,
  15. FILTER_KNOB_B,
  16. FILTER_KNOB_C,
  17. NUM_PARAMS
  18. };
  19. enum InputIds {
  20. TRIGGER_GATE,
  21. INITIAL_PACKET_INPUT,
  22. FILTER_INPUT,
  23. FREQ_CV,
  24. ATTEN_CV,
  25. FILTER_CV_A,
  26. FILTER_CV_B,
  27. FILTER_CV_C,
  28. NUM_INPUTS
  29. };
  30. enum OutputIds {
  31. SYNTH_OUTPUT,
  32. NUM_OUTPUTS
  33. };
  34. enum LightIds {
  35. LIGHT_PACKET_KNOB,
  36. LIGHT_PACKET_CV,
  37. LIGHT_FILTER_KNOB,
  38. LIGHT_FILTER_CV,
  39. LIGHT_FILTER_A,
  40. LIGHT_FILTER_B,
  41. LIGHT_FILTER_C,
  42. NUM_LIGHTS
  43. };
  44. SchmittTrigger voiceTrigger;
  45. std::vector< KSSynth *> voices;
  46. const static int nVoices = 32;
  47. KarplusStrongPoly() : Module( NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS )
  48. {
  49. for( int i=0; i<nVoices; ++i ) voices.push_back( new KSSynth(-2.0f, 2.0f, engineGetSampleRate() ) );
  50. initPacketStringDirty = true;
  51. currentInitialPacket = KSSynth::RANDOM;
  52. initPacketString = voices[ 0 ]->initPacketName( currentInitialPacket );
  53. filterStringDirty = true;
  54. currentFilter = KSSynth::WEIGHTED_ONE_SAMPLE;
  55. filterString = voices[ 0 ]->filterTypeName( currentFilter );
  56. }
  57. virtual ~KarplusStrongPoly()
  58. {
  59. for( auto syn : voices )
  60. delete syn;
  61. }
  62. int getNumPackets() { return voices[ 0 ]->numInitPackets(); }
  63. KSSynth::InitPacket currentInitialPacket;
  64. int getNumFilters() { return voices[ 0 ]->numFilterTypes(); }
  65. KSSynth::FilterType currentFilter;
  66. void step() override
  67. {
  68. int nextInitialPacket = currentInitialPacket;
  69. float ipi, ipiS;
  70. lights[ LIGHT_PACKET_CV ].value = inputs[INITIAL_PACKET_INPUT].active;
  71. lights[ LIGHT_PACKET_KNOB ].value = !inputs[INITIAL_PACKET_INPUT].active;
  72. lights[ LIGHT_FILTER_CV ].value = inputs[FILTER_INPUT].active;
  73. lights[ LIGHT_FILTER_KNOB ].value = !inputs[FILTER_INPUT].active;
  74. // For now, since we only have one filter, hardcode this
  75. lights[ LIGHT_FILTER_A ].value = 1;
  76. lights[ LIGHT_FILTER_B ].value = 0;
  77. lights[ LIGHT_FILTER_C ].value = 0;
  78. if( inputs[ INITIAL_PACKET_INPUT ].active )
  79. {
  80. ipi = inputs[ INITIAL_PACKET_INPUT ].value;
  81. ipiS = ipi * getNumPackets() / 10.0;
  82. nextInitialPacket = (int)(ipiS);
  83. }
  84. else
  85. {
  86. nextInitialPacket = (int)( params[ INITIAL_PACKET ].value );
  87. }
  88. if( nextInitialPacket != currentInitialPacket )
  89. {
  90. initPacketStringDirty = true;
  91. currentInitialPacket = (KSSynth::InitPacket)( nextInitialPacket );
  92. initPacketString = voices[ 0 ]->initPacketName( currentInitialPacket );
  93. }
  94. // Check a trigger here and find a voice
  95. bool newVoice = false;
  96. if( voiceTrigger.process( inputs[ TRIGGER_GATE ].value ) )
  97. {
  98. newVoice = true;
  99. }
  100. if( newVoice )
  101. {
  102. // find voice
  103. KSSynth *voice = NULL;
  104. for( auto syn: voices )
  105. if( ! syn->active )
  106. {
  107. voice = syn;
  108. break;
  109. }
  110. if( voice == NULL )
  111. {
  112. // info( "All voices are active: Running voice steal" );
  113. voice = voices[ 0 ];
  114. float ds = voice->sumDelaySquared;
  115. for( auto syn: voices )
  116. {
  117. if( syn->sumDelaySquared < ds )
  118. {
  119. ds = syn->sumDelaySquared;
  120. voice = syn;
  121. }
  122. }
  123. }
  124. // Capture parameters onto this voice and trigger it
  125. float pitch = params[ FREQ_KNOB ].value + 12.0f * inputs[ FREQ_CV ].value;
  126. float freq = 261.262f * powf( 2.0f, pitch / 12.0f );
  127. // For now, since we only have one filter, hardcode this
  128. voice->filtParamA = clamp( params[ FILTER_KNOB_A ].value + inputs[ FILTER_CV_A ].value * 0.1, 0.0f, 1.0f );
  129. voice->filtParamB = 0;
  130. voice->filtParamC = 0;
  131. float atten = params[ ATTEN_KNOB ].value + inputs[ ATTEN_CV ].value;
  132. voice->packet = currentInitialPacket;
  133. voice->filtAtten = atten;
  134. voice->trigger( freq );
  135. }
  136. float out = 0.0f;
  137. for( auto syn : voices )
  138. if( syn->active )
  139. out += syn->step();
  140. outputs[ SYNTH_OUTPUT ].value = out;
  141. }
  142. bool initPacketStringDirty;
  143. std::string initPacketString;
  144. static bool getInitialPacketStringDirty( Module *that )
  145. {
  146. return dynamic_cast<KarplusStrongPoly *>(that)->initPacketStringDirty;
  147. }
  148. static std::string getInitialPacketString( Module *that )
  149. {
  150. dynamic_cast<KarplusStrongPoly *>(that)->initPacketStringDirty = false;
  151. return dynamic_cast<KarplusStrongPoly *>(that)->initPacketString;
  152. }
  153. bool filterStringDirty;
  154. std::string filterString;
  155. static bool getFilterStringDirty( Module *that )
  156. {
  157. return dynamic_cast<KarplusStrongPoly *>(that)->filterStringDirty;
  158. }
  159. static std::string getFilterString( Module *that )
  160. {
  161. dynamic_cast<KarplusStrongPoly *>(that)->filterStringDirty = false;
  162. return dynamic_cast<KarplusStrongPoly *>(that)->filterString;
  163. }
  164. };
  165. struct KarplusStrongPolyWidget : ModuleWidget {
  166. KarplusStrongPolyWidget( KarplusStrongPoly *module);
  167. };
  168. KarplusStrongPolyWidget::KarplusStrongPolyWidget( KarplusStrongPoly *module ) : ModuleWidget( module )
  169. {
  170. box.size = Vec( SCREW_WIDTH * 15, RACK_HEIGHT );
  171. BaconBackground *bg = new BaconBackground( box.size, "KarplusStrongPoly" );
  172. addChild( bg->wrappedInFramebuffer());
  173. float outy;
  174. float yh;
  175. int margin = 4;
  176. float gap = 13;
  177. int obuf = 10;
  178. outy = 35;
  179. float scale = 1.0;
  180. bool last = false;
  181. auto brd = [&](float ys)
  182. {
  183. // Add a downward pointing triangle here which means I need a draw glyph
  184. if( ! last )
  185. {
  186. int w = 70;
  187. addChild( new BufferedDrawLambdaWidget( Vec( bg->cx() - w/2, outy + ys + margin ),
  188. Vec( w, gap ),
  189. [=](NVGcontext *vg)
  190. {
  191. nvgBeginPath( vg );
  192. nvgMoveTo( vg, 0, 0 );
  193. nvgLineTo( vg, w/2, gap );
  194. nvgLineTo( vg, w, 0 );
  195. nvgClosePath( vg );
  196. nvgStrokeColor( vg, COLOR_BLACK );
  197. nvgStroke( vg );
  198. nvgFillColor( vg, nvgRGB( 240 * scale, 240 * scale, 200 * scale ) );
  199. nvgFill( vg );
  200. }
  201. )
  202. );
  203. }
  204. bg->addRoundedBorder( Vec( obuf, outy - margin ),
  205. Vec( box.size.x - 2 * obuf, ys + 2 * margin ),
  206. nvgRGB( 240*scale, 240*scale, 200*scale ) );
  207. scale *= 0.92;
  208. };
  209. auto cl = [&](std::string lab, float ys)
  210. {
  211. bg->addLabel( Vec( obuf + margin, outy + ys / 2 ), lab.c_str(), 13, NVG_ALIGN_MIDDLE | NVG_ALIGN_LEFT );
  212. };
  213. yh = SizeTable<PJ301MPort>::Y;
  214. brd( yh );
  215. cl( "Trigger", yh );
  216. addInput( Port::create< PJ301MPort >( Vec( box.size.x - obuf - margin - SizeTable<PJ301MPort>::X, outy ),
  217. Port::INPUT,
  218. module,
  219. KarplusStrongPoly::TRIGGER_GATE ) );
  220. outy += yh + 2 * margin + gap;
  221. yh = SizeTable<RoundBlackKnob >::Y;
  222. brd( yh );
  223. cl( "Freq", yh );
  224. int xp = box.size.x - margin - obuf - SizeTable<PJ301MPort>::X;
  225. addInput( Port::create< PJ301MPort >( Vec( xp, outy + diffY2c< RoundBlackKnob, PJ301MPort >() ),
  226. Port::INPUT,
  227. module,
  228. KarplusStrongPoly::FREQ_CV ) );
  229. xp -= SizeTable<RoundBlackKnob>::X + margin;
  230. addParam( ParamWidget::create< RoundBlackKnob >( Vec( xp, outy ), module,
  231. KarplusStrongPoly::FREQ_KNOB,
  232. -54.0f, 54.0f, 0.0f ) );
  233. outy += yh + 2 * margin + gap;
  234. yh = SizeTable<RoundBlackSnapKnob>::Y;
  235. brd( yh );
  236. cl( "Packet", yh );
  237. xp = 55;
  238. addChild( ModuleLightWidget::create< SmallLight< BlueLight > >( Vec( xp - 2, outy - 2 ),
  239. module,
  240. KarplusStrongPoly::LIGHT_PACKET_KNOB ) );
  241. addParam( ParamWidget::create< RoundBlackSnapKnob >( Vec( xp, outy ),
  242. module,
  243. KarplusStrongPoly::INITIAL_PACKET,
  244. 0,
  245. module->getNumPackets()-1, 0 ) );
  246. xp += SizeTable<RoundBlackSnapKnob>::X + margin;
  247. addChild( ModuleLightWidget::create< SmallLight< BlueLight > >( Vec( xp - 2, outy - 2 + diffY2c<RoundBlackSnapKnob,PJ301MPort>()),
  248. module,
  249. KarplusStrongPoly::LIGHT_PACKET_CV ) );
  250. addInput( Port::create<PJ301MPort>( Vec( xp, outy + diffY2c<RoundBlackSnapKnob,PJ301MPort>() ),
  251. Port::INPUT, module, KarplusStrongPoly::INITIAL_PACKET_INPUT ) );
  252. xp += SizeTable<PJ301MPort>::X + margin;
  253. addChild( DotMatrixLightTextWidget::create( Vec( xp, outy + diffY2c<RoundBlackSnapKnob,DotMatrixLightTextWidget>() ),
  254. module, 8,
  255. KarplusStrongPoly::getInitialPacketStringDirty,
  256. KarplusStrongPoly::getInitialPacketString ) );
  257. outy += yh + 2 * margin + gap;
  258. yh = SizeTable<RoundBlackSnapKnob>::Y + SizeTable<RoundBlackKnob>::Y + margin;
  259. brd( yh );
  260. cl( "Filter", SizeTable<RoundBlackKnob>::Y );
  261. xp = 55;
  262. addChild( ModuleLightWidget::create< SmallLight< BlueLight > >( Vec( xp - 2, outy - 2 ),
  263. module,
  264. KarplusStrongPoly::LIGHT_FILTER_KNOB ) );
  265. addParam( ParamWidget::create< RoundBlackSnapKnob >( Vec( xp, outy ),
  266. module,
  267. KarplusStrongPoly::FILTER_TYPE,
  268. 0,
  269. module->getNumFilters()-1, 0 ) );
  270. xp += SizeTable<RoundBlackSnapKnob>::X + margin;
  271. addChild( ModuleLightWidget::create< SmallLight< BlueLight > >( Vec( xp - 2, outy - 2 + diffY2c<RoundBlackSnapKnob,PJ301MPort>()),
  272. module,
  273. KarplusStrongPoly::LIGHT_FILTER_CV ) );
  274. addInput( Port::create<PJ301MPort>( Vec( xp, outy + diffY2c<RoundBlackSnapKnob,PJ301MPort>() ),
  275. Port::INPUT, module, KarplusStrongPoly::FILTER_INPUT ) );
  276. xp += SizeTable<PJ301MPort>::X + margin;
  277. addChild( DotMatrixLightTextWidget::create( Vec( xp, outy + diffY2c<RoundBlackSnapKnob,DotMatrixLightTextWidget>() ),
  278. module, 8,
  279. KarplusStrongPoly::getFilterStringDirty,
  280. KarplusStrongPoly::getFilterString ) );
  281. outy += SizeTable<RoundBlackKnob>::Y + 2 * margin;
  282. xp = obuf + 2.5 * margin;
  283. for( int i=0; i<3; ++i )
  284. {
  285. addChild( ModuleLightWidget::create< SmallLight< BlueLight> >( Vec( xp - 2, outy - 2 ),
  286. module,
  287. KarplusStrongPoly::LIGHT_FILTER_A + i ) );
  288. bg->addLabel( Vec( xp, outy + SizeTable<RoundSmallBlackKnob>::Y ),
  289. i == 0 ? "A" : i == 1 ? "B" : "C",
  290. 12,
  291. NVG_ALIGN_BOTTOM | NVG_ALIGN_RIGHT );
  292. xp += 3;
  293. addParam( ParamWidget::create< RoundSmallBlackKnob >( Vec( xp, outy ),
  294. module,
  295. KarplusStrongPoly::FILTER_KNOB_A + i,
  296. 0, 1, 0.5 ) );
  297. xp += SizeTable<RoundSmallBlackKnob>::X + margin;
  298. addInput( Port::create<PJ301MPort>( Vec( xp, outy + diffY2c<RoundSmallBlackKnob,PJ301MPort>() ),
  299. Port::INPUT, module, KarplusStrongPoly::FILTER_CV_A + i ) );
  300. xp += SizeTable<PJ301MPort>::X + 3.5 * margin;
  301. }
  302. outy += yh - SizeTable<RoundBlackKnob>::Y + gap;
  303. yh = SizeTable< RoundBlackKnob >::Y;
  304. brd( yh );
  305. cl( "Atten", yh );
  306. xp = box.size.x - margin - obuf - SizeTable<PJ301MPort>::X;
  307. addInput( Port::create< PJ301MPort >( Vec( xp, outy + diffY2c< RoundBlackKnob, PJ301MPort >() ),
  308. Port::INPUT,
  309. module,
  310. KarplusStrongPoly::ATTEN_CV ) );
  311. xp -= SizeTable<RoundBlackKnob>::X + margin;
  312. addParam( ParamWidget::create< RoundBlackKnob >( Vec( xp, outy ), module,
  313. KarplusStrongPoly::ATTEN_KNOB,
  314. 0.1, 4, 1.95 ) );
  315. outy += yh + 2 * margin + gap;
  316. last = true;
  317. brd( SizeTable<PJ301MPort>::Y );
  318. cl( "Output", SizeTable<PJ301MPort>::Y );
  319. addOutput( Port::create< PJ301MPort >( Vec( box.size.x - obuf - margin - SizeTable<PJ301MPort>::X, outy ),
  320. Port::OUTPUT,
  321. module,
  322. KarplusStrongPoly::SYNTH_OUTPUT ) );
  323. }
  324. } // namespace rack_plugin_BaconMusic
  325. using namespace rack_plugin_BaconMusic;
  326. RACK_PLUGIN_MODEL_INIT(BaconMusic, KarplusStrongPoly) {
  327. Model *modelKarplusStrongPoly = Model::create<KarplusStrongPoly, KarplusStrongPolyWidget>("Bacon Music", "KarplusStrongPoly", "KarplusStrongPoly", OSCILLATOR_TAG );
  328. return modelKarplusStrongPoly;
  329. }