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