2727#include "py/runtime.h"
2828#include "shared-bindings/synthio/MidiTrack.h"
2929
30- #define LOUDNESS 0x4000 // 0.5
3130#define BITS_PER_SAMPLE 16
3231#define BYTES_PER_SAMPLE (BITS_PER_SAMPLE / 8)
3332#define SILENCE 0x80
@@ -47,36 +46,51 @@ STATIC uint8_t parse_note(const uint8_t *buffer, uint32_t len, uint32_t *pos) {
4746 return note ;
4847}
4948
50- STATIC void terminate_span (synthio_miditrack_obj_t * self , uint16_t * dur , uint16_t * max_dur ) {
49+ STATIC void terminate_span (synthio_miditrack_obj_t * self , uint16_t * dur ) {
5150 if (* dur ) {
5251 self -> track [self -> total_spans - 1 ].dur = * dur ;
53- if (* dur > * max_dur ) {
54- * max_dur = * dur ;
55- }
5652 * dur = 0 ;
5753 } else {
5854 self -> total_spans -- ;
5955 }
6056}
6157
62- STATIC void add_span (synthio_miditrack_obj_t * self , uint8_t note1 , uint8_t note2 ) {
63- synthio_midi_span_t span = { 0 , {note1 , note2 } };
58+ STATIC void add_span (synthio_miditrack_obj_t * self , const synthio_midi_span_t * span ) {
6459 self -> track = m_realloc (self -> track ,
6560 (self -> total_spans + 1 ) * sizeof (synthio_midi_span_t ));
66- self -> track [self -> total_spans ++ ] = span ;
61+ self -> track [self -> total_spans ++ ] = * span ;
62+ }
63+
64+ STATIC int find_channel_with_note (const synthio_midi_span_t * span , uint8_t note ) {
65+ for (int i = 0 ; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS ; i ++ ) {
66+ if (span -> note [i ] == note ) {
67+ return i ;
68+ }
69+ }
70+ return -1 ;
71+ }
72+
73+ STATIC void change_span_note (synthio_miditrack_obj_t * self , uint8_t old_note , uint8_t new_note , uint16_t * dur ) {
74+ synthio_midi_span_t span = self -> track [self -> total_spans - 1 ];
75+ int channel = find_channel_with_note (& span , old_note );
76+ if (channel != -1 ) {
77+ terminate_span (self , dur );
78+ span .note [channel ] = new_note ;
79+ add_span (self , & span );
80+ }
6781}
6882
6983void common_hal_synthio_miditrack_construct (synthio_miditrack_obj_t * self ,
7084 const uint8_t * buffer , uint32_t len , uint32_t tempo , uint32_t sample_rate ) {
7185
72- synthio_midi_span_t initial = { 0 , {SILENCE , SILENCE } };
86+ synthio_midi_span_t initial = { 0 , {[ 0 ... ( CIRCUITPY_SYNTHIO_MAX_CHANNELS - 1 )] = SILENCE } };
7387 self -> sample_rate = sample_rate ;
7488 self -> track = m_malloc (sizeof (synthio_midi_span_t ), false);
7589 self -> next_span = 0 ;
7690 self -> total_spans = 1 ;
7791 * self -> track = initial ;
7892
79- uint16_t dur = 0 , max_dur = 0 ;
93+ uint16_t dur = 0 ;
8094 uint32_t pos = 0 ;
8195 while (pos < len ) {
8296 uint8_t c ;
@@ -91,37 +105,19 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self,
91105 raise_midi_stream_error (pos );
92106 }
93107
108+ // dur is carried over here so that if a note on/off message doesn't actually produce a change, the
109+ // underlying "span" is extended. Otherwise, it is zeroed out in the call to `terminate_span`.
94110 dur += delta * sample_rate / tempo ;
95111
96112 switch (buffer [pos ++ ] >> 4 ) {
97113 case 8 : { // Note Off
98114 uint8_t note = parse_note (buffer , len , & pos );
99-
100- // Ignore if not a note which is playing
101- synthio_midi_span_t last_span = self -> track [self -> total_spans - 1 ];
102- if (last_span .note [0 ] == note || last_span .note [1 ] == note ) {
103- terminate_span (self , & dur , & max_dur );
104- if (last_span .note [0 ] == note ) {
105- add_span (self , last_span .note [1 ], SILENCE );
106- } else {
107- add_span (self , last_span .note [0 ], SILENCE );
108- }
109- }
115+ change_span_note (self , note , SILENCE , & dur );
110116 break ;
111117 }
112118 case 9 : { // Note On
113119 uint8_t note = parse_note (buffer , len , & pos );
114-
115- // Ignore if two notes are already playing
116- synthio_midi_span_t last_span = self -> track [self -> total_spans - 1 ];
117- if (last_span .note [1 ] == SILENCE ) {
118- terminate_span (self , & dur , & max_dur );
119- if (last_span .note [0 ] == SILENCE ) {
120- add_span (self , note , SILENCE );
121- } else {
122- add_span (self , last_span .note [0 ], note );
123- }
124- }
120+ change_span_note (self , SILENCE , note , & dur );
125121 break ;
126122 }
127123 case 10 :
@@ -142,8 +138,12 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self,
142138 raise_midi_stream_error (pos );
143139 }
144140 }
145- terminate_span (self , & dur , & max_dur );
141+ terminate_span (self , & dur );
146142
143+ uint16_t max_dur = 0 ;
144+ for (int i = 0 ; i < self -> total_spans ; i ++ ) {
145+ max_dur = MAX (self -> track [i ].dur , max_dur );
146+ }
147147 self -> buffer_length = max_dur * BYTES_PER_SAMPLE ;
148148 self -> buffer = m_malloc (self -> buffer_length , false);
149149}
@@ -177,6 +177,16 @@ void synthio_miditrack_reset_buffer(synthio_miditrack_obj_t *self,
177177STATIC const uint16_t notes [] = {8372 , 8870 , 9397 , 9956 , 10548 , 11175 , 11840 ,
178178 12544 , 13290 , 14080 , 14917 , 15804 }; // 9th octave
179179
180+ static int count_active_channels (synthio_midi_span_t * span ) {
181+ int result = 0 ;
182+ for (int i = 0 ; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS ; i ++ ) {
183+ if (span -> note [i ] != SILENCE ) {
184+ result += 1 ;
185+ }
186+ }
187+ return result ;
188+ }
189+
180190audioio_get_buffer_result_t synthio_miditrack_get_buffer (synthio_miditrack_obj_t * self ,
181191 bool single_channel_output , uint8_t channel , uint8_t * * buffer , uint32_t * buffer_length ) {
182192
@@ -187,19 +197,26 @@ audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t
187197
188198 synthio_midi_span_t span = self -> track [self -> next_span ++ ];
189199 * buffer_length = span .dur * BYTES_PER_SAMPLE ;
190- uint8_t octave1 = span .note [0 ] / 12 ; // 0..10
191- uint8_t octave2 = span .note [1 ] / 12 ; // 0..10
192- int32_t base_freq1 = notes [span .note [0 ] % 12 ];
193- int32_t base_freq2 = notes [span .note [1 ] % 12 ];
194- int32_t sample_rate = self -> sample_rate ;
200+ memset (self -> buffer , 0 , * buffer_length );
195201
196- for (uint16_t i = 0 ; i < span .dur ; i ++ ) {
197- int16_t semiperiod1 = span .note [0 ] == SILENCE ? 0 :
198- ((base_freq1 * i * 2 ) / sample_rate ) >> (10 - octave1 );
199- int16_t semiperiod2 = span .note [1 ] == SILENCE ? semiperiod1 :
200- ((base_freq2 * i * 2 ) / sample_rate ) >> (10 - octave2 );
201- self -> buffer [i ] = ((semiperiod1 % 2 + semiperiod2 % 2 ) - 1 ) * LOUDNESS ;
202+ int32_t sample_rate = self -> sample_rate ;
203+ int active_channels = count_active_channels (& span );
204+ if (active_channels ) {
205+ int16_t loudness = 0x3fff / (1 + active_channels );
206+ for (int chan = 0 ; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS ; chan ++ ) {
207+ uint8_t octave = span .note [chan ] / 12 ;
208+ uint16_t base_freq = notes [span .note [chan ] % 12 ];
209+ if (span .note [chan ] == SILENCE ) {
210+ continue ;
211+ }
212+ for (uint16_t i = 0 ; i < span .dur ; i ++ ) {
213+ int16_t semiperiod =
214+ ((base_freq * i * 2 ) / sample_rate ) >> (10 - octave );
215+ self -> buffer [i ] += semiperiod % 2 ? loudness : - loudness ;
216+ }
217+ }
202218 }
219+
203220 * buffer = (uint8_t * )self -> buffer ;
204221
205222 return self -> next_span >= self -> total_spans ?
0 commit comments