1 /* GSequencer - Advanced GTK Sequencer
2  * Copyright (C) 2005-2019 Joël Krähemann
3  *
4  * This file is part of GSequencer.
5  *
6  * GSequencer is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GSequencer is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with GSequencer.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <ags/audio/midi/ags_midi_file.h>
21 
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include <unistd.h>
26 #include <sys/stat.h>
27 
28 #include <ags/i18n.h>
29 
30 void ags_midi_file_class_init(AgsMidiFileClass *midi_file);
31 void ags_midi_file_init(AgsMidiFile *midi_file);
32 void ags_midi_file_set_property(GObject *gobject,
33 				guint prop_id,
34 				const GValue *value,
35 				GParamSpec *param_spec);
36 void ags_midi_file_get_property(GObject *gobject,
37 				guint prop_id,
38 				GValue *value,
39 				GParamSpec *param_spec);
40 void ags_midi_file_finalize(GObject *gobject);
41 
42 /**
43  * SECTION:ags_midi_file
44  * @short_description: the MIDI file
45  * @title: AgsMidiFile
46  * @section_id:
47  * @include: ags/audio/midi/ags_midi_file.h
48  *
49  * #AgsMidiFile reads or writes your midi files.
50  */
51 
52 enum{
53   PROP_0,
54   PROP_FILENAME,
55 };
56 
57 static gpointer ags_midi_file_parent_class = NULL;
58 
59 GType
ags_midi_file_get_type(void)60 ags_midi_file_get_type(void)
61 {
62   static volatile gsize g_define_type_id__volatile = 0;
63 
64   if(g_once_init_enter (&g_define_type_id__volatile)){
65     GType ags_type_midi_file = 0;
66 
67     static const GTypeInfo ags_midi_file_info = {
68       sizeof (AgsMidiFileClass),
69       NULL, /* base_init */
70       NULL, /* base_finalize */
71       (GClassInitFunc) ags_midi_file_class_init,
72       NULL, /* class_finalize */
73       NULL, /* class_data */
74       sizeof (AgsMidiFile),
75       0,    /* n_preallocs */
76       (GInstanceInitFunc) ags_midi_file_init,
77     };
78 
79     ags_type_midi_file = g_type_register_static(G_TYPE_OBJECT,
80 						"AgsMidiFile", &ags_midi_file_info,
81 						0);
82 
83     g_once_init_leave(&g_define_type_id__volatile, ags_type_midi_file);
84   }
85 
86   return g_define_type_id__volatile;
87 }
88 
89 void
ags_midi_file_class_init(AgsMidiFileClass * midi_file)90 ags_midi_file_class_init(AgsMidiFileClass *midi_file)
91 {
92   GObjectClass *gobject;
93   GParamSpec *param_spec;
94 
95   ags_midi_file_parent_class = g_type_class_peek_parent(midi_file);
96 
97   /* GObjectClass */
98   gobject = (GObjectClass *) midi_file;
99 
100   gobject->set_property = ags_midi_file_set_property;
101   gobject->get_property = ags_midi_file_get_property;
102 
103   gobject->finalize = ags_midi_file_finalize;
104 
105   /* properties */
106   /**
107    * AgsMidiFile:filename:
108    *
109    * The assigned filename to perform input/output on.
110    *
111    * Since: 3.0.0
112    */
113   param_spec = g_param_spec_string("filename",
114 				   i18n_pspec("assigned filename"),
115 				   i18n_pspec("The filename to read or write"),
116 				   NULL,
117 				   G_PARAM_READABLE | G_PARAM_WRITABLE);
118   g_object_class_install_property(gobject,
119 				  PROP_FILENAME,
120 				  param_spec);
121 }
122 
123 GQuark
ags_midi_file_error_quark()124 ags_midi_file_error_quark()
125 {
126   return(g_quark_from_static_string("ags-midi-file-error-quark"));
127 }
128 
129 void
ags_midi_file_init(AgsMidiFile * midi_file)130 ags_midi_file_init(AgsMidiFile *midi_file)
131 {
132   midi_file->flags = 0;
133 
134   /* midi file mutex */
135   g_mutex_init(&(midi_file->obj_mutex));
136 
137   midi_file->file = NULL;
138   midi_file->filename = NULL;
139 
140   midi_file->buffer = NULL;
141   midi_file->buffer_length = 0;
142 
143   midi_file->offset = AGS_MIDI_FILE_DEFAULT_OFFSET;
144   midi_file->format = AGS_MIDI_FILE_DEFAULT_FORMAT;
145   midi_file->count = 0;
146   midi_file->division = (60 * AGS_USEC_PER_SEC) / AGS_MIDI_FILE_DEFAULT_BEATS;
147   midi_file->times = 0;
148   midi_file->beat = AGS_MIDI_FILE_DEFAULT_BEATS;
149   midi_file->clicks = AGS_MIDI_FILE_DEFAULT_TICKS;
150 
151   midi_file->track = NULL;
152 
153   midi_file->current_track = NULL;
154 
155   midi_file->notation = NULL;
156   midi_file->midi = NULL;
157 }
158 
159 void
ags_midi_file_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * param_spec)160 ags_midi_file_set_property(GObject *gobject,
161 			   guint prop_id,
162 			   const GValue *value,
163 			   GParamSpec *param_spec)
164 {
165   AgsMidiFile *midi_file;
166 
167   GRecMutex *midi_file_mutex;
168 
169   midi_file = AGS_MIDI_FILE(gobject);
170 
171   /* get midi file mutex */
172   midi_file_mutex = AGS_MIDI_FILE_GET_OBJ_MUTEX(midi_file);
173 
174   switch(prop_id){
175   case PROP_FILENAME:
176     {
177       gchar *filename;
178 
179       filename = g_value_get_string(value);
180 
181       g_rec_mutex_lock(midi_file_mutex);
182 
183       if(filename == midi_file->filename){
184 	g_rec_mutex_unlock(midi_file_mutex);
185 
186 	return;
187       }
188 
189       if(midi_file->filename != NULL){
190 	g_free(midi_file->filename);
191       }
192 
193       midi_file->filename = g_strdup(filename);
194 
195       g_rec_mutex_unlock(midi_file_mutex);
196     }
197     break;
198   default:
199     G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec);
200     break;
201   }
202 }
203 
204 void
ags_midi_file_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * param_spec)205 ags_midi_file_get_property(GObject *gobject,
206 			   guint prop_id,
207 			   GValue *value,
208 			   GParamSpec *param_spec)
209 {
210   AgsMidiFile *midi_file;
211 
212   GRecMutex *midi_file_mutex;
213 
214   midi_file = AGS_MIDI_FILE(gobject);
215 
216   /* get midi file mutex */
217   midi_file_mutex = AGS_MIDI_FILE_GET_OBJ_MUTEX(midi_file);
218 
219   switch(prop_id){
220   case PROP_FILENAME:
221     {
222       g_rec_mutex_lock(midi_file_mutex);
223 
224       g_value_set_string(value,
225 			 midi_file->filename);
226 
227       g_rec_mutex_unlock(midi_file_mutex);
228     }
229     break;
230   default:
231     G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec);
232     break;
233   }
234 }
235 
236 void
ags_midi_file_finalize(GObject * gobject)237 ags_midi_file_finalize(GObject *gobject)
238 {
239   AgsMidiFile *midi_file;
240 
241   midi_file = (AgsMidiFile *) gobject;
242 
243   g_free(midi_file->filename);
244 
245   /* call parent */
246   G_OBJECT_CLASS(ags_midi_file_parent_class)->finalize(gobject);
247 }
248 
249 /**
250  * ags_midi_file_open:
251  * @midi_file: the #AgsMidiFile
252  * @filename: the filename
253  *
254  * Opens a MIDI file read-only.
255  *
256  * Returns: %TRUE on success, otherwise %FALSE
257  *
258  * Since: 3.0.0
259  */
260 gboolean
ags_midi_file_open(AgsMidiFile * midi_file,gchar * filename)261 ags_midi_file_open(AgsMidiFile *midi_file,
262 		   gchar *filename)
263 {
264   if(!AGS_IS_MIDI_FILE(midi_file) ||
265      midi_file->filename == NULL){
266     return(FALSE);
267   }
268 
269   g_object_set(midi_file,
270 	       "filename", filename,
271 	       NULL);
272 
273   if(!g_file_test(filename,
274 		  (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))){
275     return(FALSE);
276   }
277 
278   midi_file->file = fopen(midi_file->filename,
279 			  "r");
280 
281   return(TRUE);
282 }
283 
284 /**
285  * ags_midi_file_open_from_data:
286  * @midi_file: the #AgsMidiFile
287  * @data: the buffer to set
288  * @buffer_length: the length of the buffer
289  *
290  * Opens a virtual MIDI file residing in @data's array.
291  *
292  * Returns: %TRUE on success, otherwise %FALSE
293  *
294  * Since: 3.0.0
295  */
296 gboolean
ags_midi_file_open_from_data(AgsMidiFile * midi_file,unsigned char * data,guint buffer_length)297 ags_midi_file_open_from_data(AgsMidiFile *midi_file,
298 			     unsigned char *data, guint buffer_length)
299 {
300   if(!AGS_IS_MIDI_FILE(midi_file)){
301     return(FALSE);
302   }
303 
304   midi_file->buffer = data;
305   midi_file->buffer_length = buffer_length;
306 
307   return(TRUE);
308 }
309 
310 /**
311  * ags_midi_file_rw_open:
312  * @midi_file: the #AgsMidiFile
313  * @filename: the filename
314  * @create: %TRUE create file if not exists, else if %FALSE return
315  *
316  * Opens a MIDI file with read-write permission.
317  *
318  * Returns: %TRUE on success, otherwise %FALSE
319  *
320  * Since: 3.0.0
321  */
322 gboolean
ags_midi_file_rw_open(AgsMidiFile * midi_file,gchar * filename,gboolean create)323 ags_midi_file_rw_open(AgsMidiFile *midi_file,
324 		      gchar *filename,
325 		      gboolean create)
326 {
327   if(!AGS_IS_MIDI_FILE(midi_file) ||
328      filename == NULL){
329     return(FALSE);
330   }
331 
332   g_object_set(midi_file,
333 	       "filename", filename,
334 	       NULL);
335 
336   if(!create &&
337      !g_file_test(filename,
338 		  (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))){
339     return(FALSE);
340   }
341 
342   midi_file->file = fopen(midi_file->filename,
343 			  "r+");
344 
345   return(TRUE);
346 }
347 
348 /**
349  * ags_midi_file_close:
350  * @midi_file: the #AgsMidiFile
351  *
352  * Closes the file stream.
353  *
354  * Since: 3.0.0
355  */
356 void
ags_midi_file_close(AgsMidiFile * midi_file)357 ags_midi_file_close(AgsMidiFile *midi_file)
358 {
359   if(!AGS_IS_MIDI_FILE(midi_file) ||
360      midi_file->file == NULL){
361     return;
362   }
363 
364   fclose(midi_file->file);
365 
366   midi_file->file = NULL;
367 }
368 
369 /**
370  * ags_midi_file_read:
371  * @midi_file: the #AgsMidiFile
372  *
373  * Reads all bytes of the file's stream and stores them in the internal buffer.
374  *
375  * Returns: the data array just read
376  *
377  * Since: 3.0.0
378  */
379 unsigned char*
ags_midi_file_read(AgsMidiFile * midi_file)380 ags_midi_file_read(AgsMidiFile *midi_file)
381 {
382   struct stat sb;
383 
384   size_t n_read;
385 
386   if(!AGS_IS_MIDI_FILE(midi_file) ||
387      midi_file->filename == NULL ||
388      midi_file->file == NULL){
389     return(NULL);
390   }
391 
392   stat(midi_file->filename, &sb);
393 
394   if(sb.st_size == 0){
395     return(NULL);
396   }
397 
398   midi_file->buffer_length = sb.st_size + 1;
399   midi_file->buffer = (unsigned char *) malloc(midi_file->buffer_length * sizeof(unsigned char));
400   midi_file->buffer[sb.st_size] = EOF;
401   n_read = fread(midi_file->buffer, sizeof(unsigned char), sb.st_size, midi_file->file);
402 
403   if(n_read != sb.st_size){
404     g_critical("fread() number of bytes read doesn't match buffer size");
405   }
406 
407   midi_file->iter = midi_file->buffer;
408 
409   return(midi_file->buffer);
410 }
411 
412 /**
413  * ags_midi_file_write:
414  * @midi_file: the #AgsMidiFile
415  * @data: the data array to write
416  * @buffer_length: n-bytes to write
417  *
418  * Writes @data to the file stream and to internal buffer, reallocates it if necessary.
419  *
420  * Since: 3.0.0
421  */
422 void
ags_midi_file_write(AgsMidiFile * midi_file,unsigned char * data,guint buffer_length)423 ags_midi_file_write(AgsMidiFile *midi_file,
424 		    unsigned char *data, guint buffer_length)
425 {
426   unsigned char *start;
427 
428   if(!AGS_IS_MIDI_FILE(midi_file) ||
429      midi_file->file == NULL){
430     return;
431   }
432 
433   if(midi_file->buffer == NULL){
434     start =
435       midi_file->buffer = (unsigned char *) malloc((buffer_length + 1) * sizeof(unsigned char));
436     midi_file->buffer_length = buffer_length + 1;
437   }else{
438     guint old_buffer_length;
439 
440     old_buffer_length = midi_file->buffer_length;
441 
442     midi_file->buffer = realloc(midi_file->buffer,
443 				(old_buffer_length + buffer_length) * sizeof(unsigned char));
444     start = &(midi_file->buffer[old_buffer_length - 1]);
445   }
446 
447   memcpy(start, data, buffer_length * sizeof(unsigned char));
448   start[buffer_length] = EOF;
449 
450   fwrite(data, sizeof(unsigned char), buffer_length, midi_file->file);
451 }
452 
453 /**
454  * ags_midi_file_seek:
455  * @midi_file: the #AgsMidiFile
456  * @position: the offset
457  * @whence: SEEK_SET, SEEK_END, or SEEK_CUR
458  *
459  * Seeks the file stream's offset.
460  *
461  * Since: 3.0.0
462  */
463 void
ags_midi_file_seek(AgsMidiFile * midi_file,guint position,gint whence)464 ags_midi_file_seek(AgsMidiFile *midi_file, guint position, gint whence)
465 {
466   if(!AGS_IS_MIDI_FILE(midi_file) ||
467      midi_file->file == NULL){
468     return;
469   }
470 
471   fseek(midi_file->file, position, whence);
472 }
473 
474 /**
475  * ags_midi_file_flush:
476  * @midi_file: the #AgsMidiFile
477  *
478  * Flushes file stream's data buffer to disc.
479  *
480  * Since: 3.0.0
481  */
482 void
ags_midi_file_flush(AgsMidiFile * midi_file)483 ags_midi_file_flush(AgsMidiFile *midi_file)
484 {
485   if(!AGS_IS_MIDI_FILE(midi_file) ||
486      midi_file->file == NULL){
487     return;
488   }
489 
490   fflush(midi_file->file);
491 }
492 
493 /**
494  * ags_midi_file_read_byte:
495  * @midi_file: the #AgsMidiFile
496  * @error: the #GError pointer return location
497  *
498  * Reads a unsigned char quantity.
499  *
500  * Returns: the current value at file's iteration pointer
501  *
502  * Since: 3.0.0
503  */
504 unsigned char
ags_midi_file_read_byte(AgsMidiFile * midi_file,GError ** error)505 ags_midi_file_read_byte(AgsMidiFile *midi_file,
506 			GError **error)
507 {
508   unsigned char value;
509 
510   if(!AGS_IS_MIDI_FILE(midi_file)){
511     return(0x0);
512   }
513 
514   if(midi_file->iter + 1 < midi_file->buffer + midi_file->buffer_length){
515     value = (midi_file->iter[0]);
516 
517     midi_file->iter += 1;
518   }else{
519     if(error != NULL){
520       g_set_error(error,
521 		  AGS_MIDI_FILE_ERROR,
522 		  AGS_MIDI_FILE_ERROR_PREMATURE_EOF,
523 		  "no more data available in file buffer");
524     }
525 
526     return(0x0);
527   }
528 
529   return(value);
530 }
531 
532 /**
533  * ags_midi_file_read_gint16:
534  * @midi_file: the #AgsMidiFile
535  * @error: the #GError pointer return location
536  *
537  * Reads a gint16 quantity.
538  *
539  * Returns: the current value at file's iteration pointer
540  *
541  * Since: 3.0.0
542  */
543 gint16
ags_midi_file_read_gint16(AgsMidiFile * midi_file,GError ** error)544 ags_midi_file_read_gint16(AgsMidiFile *midi_file,
545 			  GError **error)
546 {
547   unsigned char str[2];
548   gint16 value = 0;
549 
550   if(!AGS_IS_MIDI_FILE(midi_file)){
551     return(0x0);
552   }
553 
554   if(midi_file->iter + 2 < midi_file->buffer + midi_file->buffer_length){
555     str[0] = (midi_file->iter[0]);
556     str[1] = (midi_file->iter[1]);
557 
558     midi_file->iter += 2;
559 
560     value = (str[0] & 0xff);
561     value = (value<<8) + (str[1] & 0xff);
562   }else{
563     if(error != NULL){
564       g_set_error(error,
565 		  AGS_MIDI_FILE_ERROR,
566 		  AGS_MIDI_FILE_ERROR_PREMATURE_EOF,
567 		  "no more data available in file buffer");
568     }
569 
570     return(0x0);
571   }
572 
573   return(value);
574 }
575 
576 /**
577  * ags_midi_file_read_gint24:
578  * @midi_file: the #AgsMidiFile
579  * @error: the #GError pointer return location
580  *
581  * Reads a 24-bit quantity.
582  *
583  * Returns: the current value at file's iteration pointer
584  *
585  * Since: 3.0.0
586  */
587 gint32
ags_midi_file_read_gint24(AgsMidiFile * midi_file,GError ** error)588 ags_midi_file_read_gint24(AgsMidiFile *midi_file,
589 			  GError **error)
590 {
591   unsigned char str[4];
592   gint32 value = 0;
593 
594   if(!AGS_IS_MIDI_FILE(midi_file)){
595     return(0x0);
596   }
597 
598   if(midi_file->iter + 3 < midi_file->buffer + midi_file->buffer_length){
599     str[0] = (unsigned char) 0x00;
600     str[1] = (midi_file->iter[0]);
601     str[2] = (midi_file->iter[1]);
602     str[3] = (midi_file->iter[2]);
603 
604     midi_file->iter += 3;
605 
606     value = (value<<8) + (str[1] & 0xff);
607     value = (value<<8) + (str[2] & 0xff);
608     value = (value<<8) + (str[3] & 0xff);
609   }else{
610     if(error != NULL){
611       g_set_error(error,
612 		  AGS_MIDI_FILE_ERROR,
613 		  AGS_MIDI_FILE_ERROR_PREMATURE_EOF,
614 		  "no more data available in file buffer");
615     }
616 
617     return(0x0);
618   }
619 
620   return(value);
621 }
622 
623 /**
624  * ags_midi_file_read_gint32:
625  * @midi_file: the #AgsMidiFile
626  * @error: the #GError pointer return location
627  *
628  * Reads a gint32 quantity.
629  *
630  * Returns: the current value at file's iteration pointer
631  *
632  * Since: 3.0.0
633  */
634 gint32
ags_midi_file_read_gint32(AgsMidiFile * midi_file,GError ** error)635 ags_midi_file_read_gint32(AgsMidiFile *midi_file,
636 			  GError **error)
637 {
638   unsigned char str[4];
639   gint32 value;
640 
641   if(!AGS_IS_MIDI_FILE(midi_file)){
642     return(0x0);
643   }
644 
645   if(midi_file->iter + 4 < midi_file->buffer + midi_file->buffer_length){
646     str[0] = (midi_file->iter[0]);
647     str[1] = (midi_file->iter[1]);
648     str[2] = (midi_file->iter[2]);
649     str[3] = (midi_file->iter[3]);
650 
651     midi_file->iter += 4;
652 
653     value = (str[0] & 0xff);
654     value = (value<<8) + (str[1] & 0xff);
655     value = (value<<8) + (str[2] & 0xff);
656     value = (value<<8) + (str[3] & 0xff);
657   }else{
658     if(error != NULL){
659       g_set_error(error,
660 		  AGS_MIDI_FILE_ERROR,
661 		  AGS_MIDI_FILE_ERROR_PREMATURE_EOF,
662 		  "no more data available in file buffer");
663     }
664 
665     return(0x0);
666   }
667 
668   return(value);
669 }
670 
671 /**
672  * ags_midi_file_read_varlength:
673  * @midi_file: the #AgsMidiFile
674  * @error: the #GError pointer return location
675  *
676  * Reads a variable length quantity.
677  *
678  * Returns: the current value at file's iteration pointer
679  *
680  * Since: 3.0.0
681  */
682 long
ags_midi_file_read_varlength(AgsMidiFile * midi_file,GError ** error)683 ags_midi_file_read_varlength(AgsMidiFile *midi_file,
684 			     GError **error)
685 {
686   long value;
687   guint i;
688   unsigned char c;
689   gboolean success;
690 
691   if(!AGS_IS_MIDI_FILE(midi_file)){
692     return(0x0);
693   }
694 
695   c = 0x0;
696   success = TRUE;
697 
698   if(midi_file->iter + 1 < midi_file->buffer + midi_file->buffer_length){
699     c = midi_file->iter[0];
700     value = c;
701     i = 1;
702 
703     midi_file->iter += 1;
704   }else{
705     success = FALSE;
706   }
707 
708   if((c & 0x80) &&
709      success){
710     value &= 0x7F;
711 
712     do{
713       if(midi_file->iter + 1 < midi_file->buffer + midi_file->buffer_length){
714 	//TODO:JK: unsafe
715 	value = (value << 7) + ((c = (midi_file->iter[0])) & 0x7F);
716 	i++;
717 	midi_file->iter += 1;
718       }else{
719 	success = FALSE;
720       }
721     }while((c & 0x80) && success);
722   }
723 
724   if(!success){
725     if(error != NULL){
726       g_set_error(error,
727 		  AGS_MIDI_FILE_ERROR,
728 		  AGS_MIDI_FILE_ERROR_PREMATURE_EOF,
729 		  "no more data available in file buffer");
730     }
731 
732     return(0x0);
733   }
734 
735   return(value);
736 }
737 
738 /**
739  * ags_midi_file_read_text:
740  * @midi_file: the #AgsMidiFile
741  * @length: the number of bytes to be read, or as long valid string for -1
742  * @error: the #GError pointer return location
743  *
744  * Reads a string.
745  *
746  * Returns: the string at file's iteration pointer
747  *
748  * Since: 3.0.0
749  */
750 unsigned char*
ags_midi_file_read_text(AgsMidiFile * midi_file,gint length,GError ** error)751 ags_midi_file_read_text(AgsMidiFile *midi_file,
752 			gint length,
753 			GError **error)
754 {
755   unsigned char *text;
756   gchar c;
757   guint i;
758 
759   if(!AGS_IS_MIDI_FILE(midi_file)){
760     return(NULL);
761   }
762 
763   if(midi_file->iter + length >= midi_file->buffer + midi_file->buffer_length){
764     if(error != NULL){
765       g_set_error(error,
766 		  AGS_MIDI_FILE_ERROR,
767 		  AGS_MIDI_FILE_ERROR_PREMATURE_EOF,
768 		  "no more data available in file buffer");
769     }
770 
771     return(NULL);
772   }
773 
774   text = (unsigned char *) malloc((AGS_MIDI_FILE_MAX_TEXT_LENGTH + 1) * sizeof(unsigned char));
775   memset(text, 0, AGS_MIDI_FILE_MAX_TEXT_LENGTH * sizeof(unsigned char));
776   i = 0;
777 
778   while((length <= 0 ||
779 	 i < length) && (c = (midi_file->iter[0])) != EOF){
780     midi_file->iter += 1;
781     //TODO:JK: unsafe
782     if(c == '\0' || !(g_ascii_isalnum(c) ||
783 		      g_ascii_ispunct(c) ||
784 		      c == ' ')){
785       break;
786     }
787 
788     text[i] = c;
789     i++;
790   }
791 
792   text[i] = '\0';
793 
794   return(text);
795 }
796 
797 /**
798  * ags_midi_file_write_byte:
799  * @midi_file: the #AgsMidiFile
800  * @val: the value to write
801  *
802  * Writes a unsigned char quantity to internal buffer.
803  *
804  * Since: 3.0.0
805  */
806 void
ags_midi_file_write_byte(AgsMidiFile * midi_file,unsigned char val)807 ags_midi_file_write_byte(AgsMidiFile *midi_file, unsigned char val)
808 {
809   if(midi_file->iter + 1 >= (midi_file->buffer + midi_file->buffer_length)){
810     size_t new_length;
811 
812     new_length = (midi_file->iter + 1) - (midi_file->buffer + midi_file->buffer_length);
813 
814     midi_file->buffer = (unsigned char *) realloc(midi_file->buffer,
815 						  new_length * sizeof(unsigned char));
816 
817     midi_file->buffer_length = new_length;
818   }
819 
820   midi_file->iter[0] = val;
821 
822   midi_file->iter += 1;
823 }
824 
825 /**
826  * ags_midi_file_write_gint16:
827  * @midi_file: the #AgsMidiFile
828  * @val: the value to write
829  *
830  * Writes a gint16 quantity to internal buffer.
831  *
832  * Since: 3.0.0
833  */
834 void
ags_midi_file_write_gint16(AgsMidiFile * midi_file,gint16 val)835 ags_midi_file_write_gint16(AgsMidiFile *midi_file, gint16 val)
836 {
837   if((midi_file->iter + 2) >= (midi_file->buffer + midi_file->buffer_length)){
838     size_t new_length;
839 
840     new_length = (midi_file->iter + 2) - (midi_file->buffer + midi_file->buffer_length);
841 
842     midi_file->buffer = (unsigned char *) realloc(midi_file->buffer,
843 						  new_length * sizeof(unsigned char));
844 
845     midi_file->buffer_length = new_length;
846   }
847 
848   midi_file->iter[0] = 0xff & val;
849   midi_file->iter[1] = (0xff00 & val) >> 8;
850 
851   midi_file->iter += 2;
852 }
853 
854 /**
855  * ags_midi_file_write_gint24:
856  * @midi_file: the #AgsMidiFile
857  * @val: the value to write
858  *
859  * Writes a 24-bit quantity to internal buffer.
860  *
861  * Since: 3.0.0
862  */
863 void
ags_midi_file_write_gint24(AgsMidiFile * midi_file,gint32 val)864 ags_midi_file_write_gint24(AgsMidiFile *midi_file, gint32 val)
865 {
866   if((midi_file->iter + 3) >= (midi_file->buffer + midi_file->buffer_length)){
867     size_t new_length;
868 
869     new_length = (midi_file->iter + 3) - (midi_file->buffer + midi_file->buffer_length);
870 
871     midi_file->buffer = (unsigned char *) realloc(midi_file->buffer,
872 						  new_length * sizeof(unsigned char));
873 
874     midi_file->buffer_length = new_length;
875   }
876 
877   midi_file->iter[0] = 0xff & val;
878   midi_file->iter[1] = (0xff00 & val) >> 8;
879   midi_file->iter[2] = (0xff0000 & val) >> 16;
880 
881   midi_file->iter += 3;
882 }
883 
884 /**
885  * ags_midi_file_write_gint32:
886  * @midi_file: the #AgsMidiFile
887  * @val: the value to write
888  *
889  * Writes a gint32 quantity to internal buffer.
890  *
891  * Since: 3.0.0
892  */
893 void
ags_midi_file_write_gint32(AgsMidiFile * midi_file,gint32 val)894 ags_midi_file_write_gint32(AgsMidiFile *midi_file, gint32 val)
895 {
896   if((midi_file->iter + 4) >= (midi_file->buffer + midi_file->buffer_length)){
897     size_t new_length;
898 
899     new_length = (midi_file->iter + 4) - (midi_file->buffer + midi_file->buffer_length);
900 
901     midi_file->buffer = (unsigned char *) realloc(midi_file->buffer,
902 						  new_length * sizeof(unsigned char));
903 
904     midi_file->buffer_length = new_length;
905   }
906 
907   midi_file->iter[0] = 0xff & val;
908   midi_file->iter[1] = (0xff00 & val) >> 8;
909   midi_file->iter[2] = (0xff0000 & val) >> 16;
910   midi_file->iter[3] = (0xff000000 & val) >> 24;
911 
912   midi_file->iter += 4;
913 }
914 
915 /**
916  * ags_midi_file_write_varlenght:
917  * @midi_file: the #AgsMidiFile
918  * @val: the value to write
919  *
920  * Writes a variable length quantity to internal buffer.
921  *
922  * Since: 3.0.0
923  */
924 void
ags_midi_file_write_varlength(AgsMidiFile * midi_file,long val)925 ags_midi_file_write_varlength(AgsMidiFile *midi_file, long val)
926 {
927   gchar c;
928   long mask;
929   guint i, j;
930 
931   mask = 0xff;
932 
933   /* retrieve new size */
934   i = 0;
935 
936   do{
937     c = ((mask << (i * 8)) & val) >> (i * 8);
938     i++;
939   }while(0x80 & c);
940 
941   /* realloc buffer if needed */
942   if((midi_file->iter + i) >= (midi_file->buffer + midi_file->buffer_length)){
943     size_t new_length;
944 
945     new_length = (midi_file->iter + i) - (midi_file->buffer + midi_file->buffer_length);
946 
947     midi_file->buffer = (unsigned char *) realloc(midi_file->buffer,
948 						  new_length * sizeof(unsigned char));
949 
950     midi_file->buffer_length = new_length;
951   }
952 
953   /* write to internal buffer */
954   for(j = 0; j < i; i++){
955     midi_file->iter[j] = ((mask << (j * 8)) & val) >> (j * 8);
956   }
957 
958   midi_file->iter += i;
959 }
960 
961 /**
962  * ags_midi_file_write_text:
963  * @midi_file: the #AgsMidiFile
964  * @text: the text
965  * @length: the string's length
966  *
967  * Writes a string to internal buffer up to length bytes.
968  *
969  * Since: 3.0.0
970  */
971 void
ags_midi_file_write_text(AgsMidiFile * midi_file,gchar * text,guint length)972 ags_midi_file_write_text(AgsMidiFile *midi_file,
973 			 gchar *text, guint length)
974 {
975   guint i;
976 
977   if(text == NULL){
978     return;
979   }
980 
981   if((midi_file->iter + length) >= (midi_file->buffer + midi_file->buffer_length)){
982     size_t new_length;
983 
984     new_length = (midi_file->iter + length) - (midi_file->buffer + midi_file->buffer_length);
985     midi_file->buffer = (unsigned char *) realloc(midi_file->buffer,
986 						  new_length * sizeof(unsigned char));
987 
988     midi_file->buffer_length = new_length;
989   }
990 
991   for(i = 0; i < length; i++){
992     midi_file->iter[i] = text[i];
993   }
994 
995   midi_file->iter += length;
996 }
997 
998 /**
999  * ags_midi_file_read_header:
1000  * @midi_file: the #AgsMidiFile
1001  * @buffer_length: pointer to return buffer length or %NULL
1002  * @error: the #GError pointer return location
1003  *
1004  * Reads the MIDI file's header and positions internal buffer pointer just behind it.
1005  *
1006  * Returns: the header's bytes
1007  *
1008  * Since: 3.0.0
1009  */
1010 unsigned char*
ags_midi_file_read_header(AgsMidiFile * midi_file,guint * buffer_length,GError ** error)1011 ags_midi_file_read_header(AgsMidiFile *midi_file,
1012 			  guint *buffer_length,
1013 			  GError **error)
1014 {
1015   static gchar header[] = "MThd";
1016 
1017   unsigned char *data;
1018   guint length;
1019 
1020   guint n;
1021   gchar c;
1022 
1023   GError *local_error;
1024 
1025   if(!AGS_IS_MIDI_FILE(midi_file)){
1026     if(buffer_length != NULL){
1027       *buffer_length = 0;
1028     }
1029 
1030     return(NULL);
1031   }
1032 
1033   data = NULL;
1034   length = 0;
1035 
1036   /* read header */
1037   n = 0;
1038 
1039   while(n < 4 &&
1040 	(AGS_MIDI_FILE_EOF & (midi_file->flags)) == 0){
1041     c = midi_file->iter[n];
1042 
1043     if(c == header[n]){
1044       n++;
1045     }else{
1046       n = 0;
1047     }
1048   }
1049 
1050   /* position internal iteration pointer */
1051   midi_file->iter += 4;
1052   length += 4;
1053 
1054   /* get some values */
1055   local_error = NULL;
1056   midi_file->offset = (guint) ags_midi_file_read_gint32(midi_file,
1057 							&local_error);
1058 
1059   local_error = NULL;
1060   midi_file->format = (guint) ags_midi_file_read_gint16(midi_file,
1061 							&local_error);
1062 
1063   local_error = NULL;
1064   midi_file->count = (guint) ags_midi_file_read_gint16(midi_file,
1065 						       &local_error);
1066 
1067   local_error = NULL;
1068   midi_file->division = (guint) ags_midi_file_read_gint16(midi_file,
1069 							  &local_error);
1070 
1071   if((midi_file->division) & 0x8000){
1072     /* SMPTE */
1073     midi_file->times = 0; /* Can't do beats */
1074   }
1075 
1076   midi_file->beat =
1077     midi_file->clicks = midi_file->division;
1078 
1079   length += 10;
1080 
1081   /* return values */
1082   if(buffer_length != NULL){
1083     *buffer_length = length;
1084   }
1085 
1086   return(data);
1087 }
1088 
1089 /**
1090  * ags_midi_file_write_header:
1091  * @midi_file: the #AgsMidiFile
1092  * @buffer: the buffer to write
1093  * @buffer_length: the length of the buffer
1094  *
1095  * Write header bytes.
1096  *
1097  * Since: 3.0.0
1098  */
1099 void
ags_midi_file_write_header(AgsMidiFile * midi_file,unsigned char * buffer,guint length)1100 ags_midi_file_write_header(AgsMidiFile *midi_file,
1101 			   unsigned char *buffer, guint length)
1102 {
1103   guint i;
1104 
1105   if(!AGS_IS_MIDI_FILE(midi_file)){
1106     return;
1107   }
1108 
1109   if((midi_file->iter + length) >= (midi_file->buffer + midi_file->buffer_length)){
1110     size_t new_length;
1111 
1112     new_length = (midi_file->iter + length) - (midi_file->buffer + midi_file->buffer_length);
1113     midi_file->buffer = (unsigned char *) realloc(midi_file->buffer,
1114 						  new_length * sizeof(unsigned char));
1115 
1116     midi_file->buffer_length = new_length;
1117   }
1118 
1119   for(i = 0; i < length; i++){
1120     midi_file->iter[i] = buffer[i];
1121   }
1122 
1123   midi_file->iter += length;
1124 }
1125 
1126 /**
1127  * ags_midi_file_read_track_data:
1128  * @midi_file: the #AgsMidiFile
1129  * @buffer_length: pointer to return buffer length or %NULL
1130  * @error: the #GError pointer return location
1131  *
1132  * Reads the MIDI file's track data.
1133  *
1134  * Returns: the track's bytes
1135  *
1136  * Since: 3.0.0
1137  */
1138 unsigned char*
ags_midi_file_read_track_data(AgsMidiFile * midi_file,guint * buffer_length,GError ** error)1139 ags_midi_file_read_track_data(AgsMidiFile *midi_file,
1140 			      guint *buffer_length,
1141 			      GError **error)
1142 {
1143   gchar *track_name;
1144   unsigned char *data, *start;
1145   guint length;
1146 
1147   long delta_time;
1148   guint status;
1149   guint n;
1150   gchar c;
1151   gboolean end_of_track;
1152 
1153   static gchar track[] = "MTrk";
1154 
1155   GError *local_error;
1156 
1157   if(!AGS_IS_MIDI_FILE(midi_file)){
1158     if(buffer_length != NULL){
1159       *buffer_length = 0;
1160     }
1161 
1162     return(NULL);
1163   }
1164 
1165   track_name = NULL;
1166   data = NULL;
1167   length = 0;
1168 
1169   /* midi track */
1170   n = 0;
1171 
1172   while(n < 4 &&
1173 	midi_file->iter < &(midi_file->buffer[midi_file->buffer_length])){
1174     c = midi_file->iter[0];
1175     midi_file->iter += 1;
1176 
1177     if(c == track[n]){
1178       n++;
1179     }else{
1180       n = 0;
1181     }
1182   }
1183 
1184   start = midi_file->iter - 4;
1185 
1186   /* offset */
1187   local_error = NULL;
1188   ags_midi_file_read_gint32(midi_file,
1189 			    &local_error);
1190 
1191   end_of_track = FALSE;
1192 
1193   while(!end_of_track){
1194     local_error = NULL;
1195     delta_time = ags_midi_file_read_varlength(midi_file,
1196 					      &local_error);
1197 
1198     status = midi_file->iter[0];
1199     midi_file->iter += 1;
1200 
1201     if((0xf0 & (0xf0 & status)) != 0xf0){
1202 #ifdef AGS_DEBUG
1203       g_message("channel message");
1204 #endif
1205     }else{
1206 #ifdef AGS_DEBUG
1207       g_message("status message");
1208 #endif
1209 
1210       switch(status){
1211       case 0xf0:
1212 	{
1213 	  /* start of system exclusive */
1214 	  while(midi_file->iter[0] != 0xf7 &&
1215 		midi_file->iter < &(midi_file->buffer[midi_file->buffer_length])){
1216 	    midi_file->iter += 1;
1217 	  }
1218 	}
1219       case 0xf1:
1220 	{
1221 	  /* quarter frame */
1222 	  midi_file->iter += 1;
1223 	}
1224 	break;
1225       case 0xf2:
1226 	{
1227 	  /* song position */
1228 	  midi_file->iter += 2;
1229 	}
1230 	break;
1231       case 0xf3:
1232 	{
1233 	  /* song select */
1234 	  midi_file->iter += 1;
1235 	}
1236 	break;
1237       case 0xf4:
1238       case 0xf5:
1239 	{
1240 	  /* undefined */
1241 	}
1242 	break;
1243       case 0xf6:
1244 	{
1245 	  /* tune request */
1246 	}
1247 	break;
1248       case 0xf7:
1249 	{
1250 	  /* sysex continuation or arbitrary stuff */
1251 #ifdef AGS_DEBUG
1252 	  g_message("sysex end");
1253 #endif
1254 	}
1255 	break;
1256       case 0xff:
1257 	{
1258 	  guint meta_type;
1259 
1260 	  /* meta event */
1261 	  meta_type = midi_file->iter[0];
1262 	  midi_file->iter += 1;
1263 
1264 	  switch(meta_type){
1265 	  case 0x00:
1266 	    {
1267 	      int c;
1268 
1269 	      c = midi_file->iter[0];
1270 	      midi_file->iter += 1;
1271 
1272 	      if(c == 0x02){
1273 		midi_file->iter += 2;
1274 	      }
1275 	    }
1276 	    break;
1277 	  case 0x01:      /* Text event */
1278 	  case 0x02:      /* Copyright notice */
1279 	  case 0x03:      /* Sequence/Track name */
1280 	  case 0x04:      /* Instrument name */
1281 	  case 0x05:      /* Lyric */
1282 	  case 0x06:      /* Marker */
1283 	  case 0x07:      /* Cue point */
1284 	  case 0x08:
1285 	  case 0x09:
1286 	  case 0x0a:
1287 	  case 0x0b:
1288 	  case 0x0c:
1289 	  case 0x0d:
1290 	  case 0x0e:
1291 	  case 0x0f:
1292 	    {
1293 	      gchar *text;
1294 	      guint text_length;
1295 
1296 	      /* These are all text events */
1297 	      local_error = NULL;
1298 	      text_length = ags_midi_file_read_varlength(midi_file,
1299 							 &local_error);
1300 
1301 	      local_error = NULL;
1302 	      text = ags_midi_file_read_text(midi_file,
1303 					     text_length,
1304 					     &local_error);
1305 
1306 	      g_free(text);
1307 	    }
1308 	    break;
1309 	  case 0x2f:
1310 	    {
1311 	      int c;
1312 
1313 	      c = midi_file->iter[0];
1314 	      midi_file->iter += 1;
1315 
1316 	      if(c == 0x0){
1317 		/* End of Track */
1318 		end_of_track = TRUE;
1319 	      }
1320 	    }
1321 	    break;
1322 	  case 0x51:
1323 	    {
1324 	      int c;
1325 
1326 	      c = midi_file->iter[0];
1327 	      midi_file->iter += 1;
1328 
1329 	      if(c == 0x03){
1330 		/* Set tempo */
1331 		midi_file->iter += 3;
1332 	      }
1333 	    }
1334 	    break;
1335 	  case 0x54:
1336 	    {
1337 	      int c;
1338 
1339 	      c = midi_file->iter[0];
1340 	      midi_file->iter += 1;
1341 
1342 	      if(c == 0x05){
1343 		midi_file->iter += 5;
1344 	      }
1345 	    }
1346 	    break;
1347 	  case 0x58:
1348 	    {
1349 	      int c;
1350 
1351 	      c = midi_file->iter[0];
1352 	      midi_file->iter += 1;
1353 
1354 	      if(c == 0x04){
1355 		/* time signature */
1356 		midi_file->iter += 4;
1357 	      }
1358 	    }
1359 	    break;
1360 	  case 0x59:
1361 	    {
1362 	      int c;
1363 
1364 	      c = midi_file->iter[0];
1365 	      midi_file->iter += 1;
1366 
1367 	      if(c == 0x02){
1368 		/* key signature */
1369 		midi_file->iter += 2;
1370 	      }
1371 	    }
1372 	    break;
1373 	  case 0x7f:
1374 	    {
1375 	      /* sequencer meta event */
1376 	      midi_file->iter += 3;
1377 	    }
1378 	    break;
1379 	  default:
1380 	    {
1381 	      /* misc */
1382 	    }
1383 	  }
1384 	}
1385 	break;
1386       default:
1387 	g_warning("bad byte");
1388 	break;
1389       }
1390     }
1391   }
1392 
1393   /* return value */
1394   length = midi_file->iter - start;
1395 
1396   if(buffer_length != NULL){
1397     *buffer_length = length;
1398   }
1399 
1400   data = malloc(length * sizeof(unsigned char));
1401   memcpy(data, start, length * sizeof(unsigned char));
1402 
1403   return(data);
1404 }
1405 
1406 void
ags_midi_file_write_track_data(AgsMidiFile * midi_file,unsigned char * buffer,guint length)1407 ags_midi_file_write_track_data(AgsMidiFile *midi_file,
1408 			       unsigned char *buffer, guint length)
1409 {
1410   guint i;
1411 
1412   if(!AGS_IS_MIDI_FILE(midi_file)){
1413     return;
1414   }
1415 
1416   if((midi_file->iter + length) >= (midi_file->buffer + midi_file->buffer_length)){
1417     size_t new_length;
1418 
1419     new_length = (midi_file->iter + length) - (midi_file->buffer + midi_file->buffer_length);
1420     midi_file->buffer = (unsigned char *) realloc(midi_file->buffer,
1421 						  new_length * sizeof(unsigned char));
1422 
1423     midi_file->buffer_length = new_length;
1424   }
1425 
1426   for(i = 0; i < length; i++){
1427     midi_file->iter[i] = buffer[i];
1428   }
1429 
1430   midi_file->iter += length;
1431 }
1432 
1433 void
ags_midi_file_read_notation(AgsMidiFile * midi_file)1434 ags_midi_file_read_notation(AgsMidiFile *midi_file)
1435 {
1436   //TODO:JK: implement me
1437 }
1438 
1439 void
ags_mid_file_read_midi(AgsMidiFile * midi_file)1440 ags_mid_file_read_midi(AgsMidiFile *midi_file)
1441 {
1442   //TODO:JK: implement me
1443 }
1444 
1445 /**
1446  * ags_midi_file_new:
1447  * @filename: the filename
1448  *
1449  * Create a new instance of #AgsMidiFile
1450  *
1451  * Returns: the new #AgsMidiFile
1452  *
1453  * Since: 3.0.0
1454  */
1455 AgsMidiFile*
ags_midi_file_new(gchar * filename)1456 ags_midi_file_new(gchar *filename)
1457 {
1458   AgsMidiFile *midi_file;
1459 
1460   midi_file = (AgsMidiFile *) g_object_new(AGS_TYPE_MIDI_FILE,
1461 					   "filename", filename,
1462 					   NULL);
1463 
1464   return(midi_file);
1465 }
1466